Simplify stash module

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-17 08:42:39 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-17 08:42:39 +0200
Commit c376832832ab8334d02e8d5e36e6e7a25b466761 (patch)
-rw-r--r-- DOCS.md 9
-rw-r--r-- examples/stash_test.lua 13
-rw-r--r-- stash.c 175
3 files changed, 78 insertions, 119 deletions
diff --git a/DOCS.md b/DOCS.md
...
506
## stash
506
## stash
507
In-memory key-value store with TTL support, powered by SQLite.
507
In-memory key-value store with TTL support, powered by SQLite.
508
  
508
  
509
- `stash.use(id, [cleanup_interval])`: Switches to database `id` (0-15). `cleanup_interval` (default 60s) sets how often expired keys are purged.
509
- `stash.cleanup(interval)`: Sets how often (in seconds) expired keys are purged (default 60s).
510
- `stash.set(key, value, [ttl])`: Stores a value. `ttl` is in seconds. If `0` or omitted, the key never expires.
510
- `stash.set(key, value, [ttl])`: Stores a value. `ttl` is in seconds. If `0` or omitted, the key never expires.
511
- `stash.get(key)`: Retrieves a value. Returns `nil` if the key is missing or expired.
511
- `stash.get(key)`: Retrieves a value. Returns `nil` if the key is missing or expired.
512
- `stash.del(key)`: Deletes a key.
512
- `stash.del(key)`: Deletes a key.
...
516
- `stash.keys([pattern])`: Returns an array of keys matching the SQL LIKE pattern (default `"%"`).
516
- `stash.keys([pattern])`: Returns an array of keys matching the SQL LIKE pattern (default `"%"`).
517
- `stash.ttl(key)`: Returns remaining seconds until expiry. `-1` means no expiry, `-2` means key not found.
517
- `stash.ttl(key)`: Returns remaining seconds until expiry. `-1` means no expiry, `-2` means key not found.
518
- `stash.expire(key, ttl)`: Updates the TTL of an existing key. Returns `true` if successful.
518
- `stash.expire(key, ttl)`: Updates the TTL of an existing key. Returns `true` if successful.
519
- `stash.clear()`: Removes all keys from the current database.
519
- `stash.clear()`: Removes all keys.
520
- `stash.count()`: Returns the number of non-expired keys.
520
- `stash.count()`: Returns the number of non-expired keys.
521
  
521
  
522
### Example: Rate Limiting
522
### Example: Rate Limiting
523
```lua
523
```lua
524
stash.use(0)
  
525
local key = "limit:" .. "127.0.0.1" -- Example IP
524
local key = "limit:" .. "127.0.0.1" -- Example IP
526
local count = stash.incr(key, 1)
525
local count = stash.incr(key, 1)
527
  
526
  
...
536
  
535
  
537
### Example: Caching
536
### Example: Caching
538
```lua
537
```lua
539
-- Use DB 0, cleanup every 30 seconds
538
-- Set cleanup every 30 seconds
540
stash.use(0, 30)
539
stash.cleanup(30)
541
  
540
  
542
-- Store a table with 5 second TTL
541
-- Store a table with 5 second TTL
543
stash.set("user:session", { id = 42, active = true }, 5)
542
stash.set("user:session", { id = 42, active = true }, 5)
...
diff --git a/examples/stash_test.lua b/examples/stash_test.lua
1
log.use_colors(true)
1
log.use_colors(true)
2
  
2
  
3
log.info("--- Testing stash.use ---")
3
log.info("--- Testing stash.cleanup ---")
4
stash.use(0, 1) -- Use DB 0, cleanup every 1s
4
stash.cleanup(1) -- cleanup every 1s
5
  
5
  
6
log.info("--- Testing stash.set and stash.get ---")
6
log.info("--- Testing stash.set and stash.get ---")
7
stash.set("name", "Luna", 0)
7
stash.set("name", "Luna", 0)
...
66
    stash.clear()
66
    stash.clear()
67
    assert.equal(0, stash.count())
67
    assert.equal(0, stash.count())
68
    log.info("Count/Clear passed")
68
    log.info("Count/Clear passed")
69
  
  
70
    log.info("--- Testing Multi-DB ---")
  
71
    stash.use(1, 60)
  
72
    stash.set("db_key", "db1", 0)
  
73
    stash.use(0, 60)
  
74
    assert.equal(nil, stash.get("db_key"))
  
75
    stash.use(1, 60)
  
76
    assert.equal("db1", stash.get("db_key"))
  
77
    log.info("Multi-DB passed")
  
78
  
69
  
79
    log.info("All stash module tests passed!")
70
    log.info("All stash module tests passed!")
80
    os.exit(0)
71
    os.exit(0)
...
diff --git a/stash.c b/stash.c
...
2
#include <sqlite3.h>
2
#include <sqlite3.h>
3
#include <time.h>
3
#include <time.h>
4
  
4
  
5
#define MAX_MEM_DBS 16
  
6
  
  
7
typedef struct {
5
typedef struct {
8
	sqlite3 *db;
6
	sqlite3 *db;
9
	ev_timer cleanup_watcher;
7
	ev_timer cleanup_watcher;
10
	int interval;
8
	int interval;
11
	int id;
  
12
} MemDB;
9
} MemDB;
13
  
10
  
14
static MemDB mem_dbs[MAX_MEM_DBS];
11
static MemDB mdb = {0};
15
static int current_db_idx = 0;
  
16
  
12
  
17
static void cleanup_cb(struct ev_loop *loop, ev_timer *w, int revents) {
13
static void cleanup_cb(struct ev_loop *loop, ev_timer *w, int revents) {
18
	MemDB *mdb = (MemDB *)w->data;
14
	if (!mdb.db) {
19
	if (!mdb->db) {
  
20
		return;
15
		return;
21
	}
16
	}
22
  
17
  
23
	sqlite3_stmt *stmt;
18
	sqlite3_stmt *stmt;
24
	const char *sql = "DELETE FROM kv WHERE e > 0 AND e < ?;";
19
	const char *sql = "DELETE FROM kv WHERE e > 0 AND e < ?;";
25
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) == SQLITE_OK) {
20
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) == SQLITE_OK) {
26
		sqlite3_bind_int64(stmt, 1, (sqlite3_int64)time(NULL));
21
		sqlite3_bind_int64(stmt, 1, (sqlite3_int64)time(NULL));
27
		sqlite3_step(stmt);
22
		sqlite3_step(stmt);
28
		sqlite3_finalize(stmt);
23
		sqlite3_finalize(stmt);
29
	}
24
	}
30
}
25
}
31
  
26
  
32
static int l_stash_use(lua_State *L) {
27
static int check_init(lua_State *L) {
33
	int id = (int)luaL_checkinteger(L, 1);
28
	if (mdb.db) {
34
	int interval = (int)luaL_optinteger(L, 2, 60);
29
		return 0;
  
30
	}
35
  
31
  
36
	if (id < 0 || id >= MAX_MEM_DBS) {
32
	if (sqlite3_open(":memory:", &mdb.db) != SQLITE_OK) {
37
		return luaL_error(L, "invalid database id (0-%d)", MAX_MEM_DBS - 1);
33
		return luaL_error(L, "failed to open in-memory database: %s", sqlite3_errmsg(mdb.db));
38
	}
34
	}
39
  
35
  
40
	current_db_idx = id;
36
	const char *schema = "CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT, e INTEGER);"
41
	MemDB *mdb = &mem_dbs[id];
37
		"CREATE INDEX IF NOT EXISTS idx_expiry ON kv(e);";
  
38
	if (sqlite3_exec(mdb.db, schema, NULL, NULL, NULL) != SQLITE_OK) {
  
39
		return luaL_error(L, "failed to create schema: %s", sqlite3_errmsg(mdb.db));
  
40
	}
42
  
41
  
43
	if (!mdb->db) {
42
	if (mdb.interval <= 0) {
44
		if (sqlite3_open(":memory:", &mdb->db) != SQLITE_OK) {
43
		mdb.interval = 60;
45
			return luaL_error(L, "failed to open in-memory database: %s", sqlite3_errmsg(mdb->db));
44
	}
46
		}
  
47
  
45
  
48
		const char *schema = "CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT, e INTEGER);"
46
	mdb.cleanup_watcher.data = &mdb;
49
			"CREATE INDEX IF NOT EXISTS idx_expiry ON kv(e);";
47
	ev_timer_init(&mdb.cleanup_watcher, cleanup_cb, mdb.interval, mdb.interval);
50
		if (sqlite3_exec(mdb->db, schema, NULL, NULL, NULL) != SQLITE_OK) {
48
	ev_timer_start(EV_DEFAULT, &mdb.cleanup_watcher);
51
			return luaL_error(L, "failed to create schema: %s", sqlite3_errmsg(mdb->db));
  
52
		}
  
53
  
49
  
54
		mdb->id = id;
50
	return 0;
55
		mdb->cleanup_watcher.data = mdb;
51
}
56
		ev_timer_init(&mdb->cleanup_watcher, cleanup_cb, interval, interval);
52
  
57
		ev_timer_start(EV_DEFAULT, &mdb->cleanup_watcher);
53
static int l_stash_cleanup(lua_State *L) {
58
	} else if (interval != mdb->interval) {
54
	int interval = (int)luaL_checkinteger(L, 1);
59
		ev_timer_stop(EV_DEFAULT, &mdb->cleanup_watcher);
55
	if (interval <= 0) {
60
		ev_timer_set(&mdb->cleanup_watcher, interval, interval);
56
		return luaL_error(L, "interval must be greater than 0");
61
		ev_timer_start(EV_DEFAULT, &mdb->cleanup_watcher);
  
62
	}
57
	}
63
  
58
  
64
	mdb->interval = interval;
59
	mdb.interval = interval;
  
60
  
  
61
	if (mdb.db) {
  
62
		ev_timer_stop(EV_DEFAULT, &mdb.cleanup_watcher);
  
63
		ev_timer_set(&mdb.cleanup_watcher, interval, interval);
  
64
		ev_timer_start(EV_DEFAULT, &mdb.cleanup_watcher);
  
65
	} else {
  
66
		check_init(L);
  
67
	}
  
68
  
65
	return 0;
69
	return 0;
66
}
70
}
67
  
71
  
68
static int l_stash_set(lua_State *L) {
72
static int l_stash_set(lua_State *L) {
  
73
	check_init(L);
69
	const char *key = luaL_checkstring(L, 1);
74
	const char *key = luaL_checkstring(L, 1);
70
	int ttl = (int)luaL_optinteger(L, 3, 0);
75
	int ttl = (int)luaL_optinteger(L, 3, 0);
71
  
76
  
72
	MemDB *mdb = &mem_dbs[current_db_idx];
  
73
	if (!mdb->db) {
  
74
		return luaL_error(L, "database not initialized, call stash.use(id) first");
  
75
	}
  
76
  
  
77
	cJSON *json = lua_to_cjson(L, 2);
77
	cJSON *json = lua_to_cjson(L, 2);
78
	char *value_str = cJSON_PrintUnformatted(json);
78
	char *value_str = cJSON_PrintUnformatted(json);
79
	cJSON_Delete(json);
79
	cJSON_Delete(json);
80
  
80
  
81
	sqlite3_stmt *stmt;
81
	sqlite3_stmt *stmt;
82
	const char *sql = "INSERT OR REPLACE INTO kv (k, v, e) VALUES (?, ?, ?);";
82
	const char *sql = "INSERT OR REPLACE INTO kv (k, v, e) VALUES (?, ?, ?);";
83
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
83
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
84
		free(value_str);
84
		free(value_str);
85
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
85
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
86
	}
86
	}
87
  
87
  
88
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
88
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
...
97
	if (sqlite3_step(stmt) != SQLITE_DONE) {
97
	if (sqlite3_step(stmt) != SQLITE_DONE) {
98
		sqlite3_finalize(stmt);
98
		sqlite3_finalize(stmt);
99
		free(value_str);
99
		free(value_str);
100
		return luaL_error(L, "failed to execute statement: %s", sqlite3_errmsg(mdb->db));
100
		return luaL_error(L, "failed to execute statement: %s", sqlite3_errmsg(mdb.db));
101
	}
101
	}
102
  
102
  
103
	sqlite3_finalize(stmt);
103
	sqlite3_finalize(stmt);
...
106
}
106
}
107
  
107
  
108
static int l_stash_get(lua_State *L) {
108
static int l_stash_get(lua_State *L) {
  
109
	check_init(L);
109
	const char *key = luaL_checkstring(L, 1);
110
	const char *key = luaL_checkstring(L, 1);
110
  
  
111
	MemDB *mdb = &mem_dbs[current_db_idx];
  
112
	if (!mdb->db) {
  
113
		return luaL_error(L, "database not initialized, call stash.use(id) first");
  
114
	}
  
115
  
111
  
116
	sqlite3_stmt *stmt;
112
	sqlite3_stmt *stmt;
117
	const char *sql = "SELECT v FROM kv WHERE k = ? AND (e > ? OR e = 0);";
113
	const char *sql = "SELECT v FROM kv WHERE k = ? AND (e > ? OR e = 0);";
118
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
114
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
119
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
115
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
120
	}
116
	}
121
  
117
  
122
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
118
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
...
140
}
136
}
141
  
137
  
142
static int l_stash_del(lua_State *L) {
138
static int l_stash_del(lua_State *L) {
  
139
	check_init(L);
143
	const char *key = luaL_checkstring(L, 1);
140
	const char *key = luaL_checkstring(L, 1);
144
  
141
  
145
	MemDB *mdb = &mem_dbs[current_db_idx];
  
146
	if (!mdb->db) {
  
147
		return luaL_error(L, "database not initialized, call stash.use(id) first");
  
148
	}
  
149
  
  
150
	sqlite3_stmt *stmt;
142
	sqlite3_stmt *stmt;
151
	const char *sql = "DELETE FROM kv WHERE k = ?;";
143
	const char *sql = "DELETE FROM kv WHERE k = ?;";
152
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
144
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
153
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
145
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
154
	}
146
	}
155
  
147
  
156
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
148
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
157
  
149
  
158
	if (sqlite3_step(stmt) != SQLITE_DONE) {
150
	if (sqlite3_step(stmt) != SQLITE_DONE) {
159
		sqlite3_finalize(stmt);
151
		sqlite3_finalize(stmt);
160
		return luaL_error(L, "failed to execute statement: %s", sqlite3_errmsg(mdb->db));
152
		return luaL_error(L, "failed to execute statement: %s", sqlite3_errmsg(mdb.db));
161
	}
153
	}
162
  
154
  
163
	sqlite3_finalize(stmt);
155
	sqlite3_finalize(stmt);
...
165
}
157
}
166
  
158
  
167
static int l_stash_exists(lua_State *L) {
159
static int l_stash_exists(lua_State *L) {
  
160
	check_init(L);
168
	const char *key = luaL_checkstring(L, 1);
161
	const char *key = luaL_checkstring(L, 1);
169
	MemDB *mdb = &mem_dbs[current_db_idx];
  
170
	if (!mdb->db) {
  
171
		return luaL_error(L, "database not initialized");
  
172
	}
  
173
  
162
  
174
	sqlite3_stmt *stmt;
163
	sqlite3_stmt *stmt;
175
	const char *sql = "SELECT 1 FROM kv WHERE k = ? AND (e > ? OR e = 0);";
164
	const char *sql = "SELECT 1 FROM kv WHERE k = ? AND (e > ? OR e = 0);";
176
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
165
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
177
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
166
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
178
	}
167
	}
179
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
168
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
180
	sqlite3_bind_int64(stmt, 2, (sqlite3_int64)time(NULL));
169
	sqlite3_bind_int64(stmt, 2, (sqlite3_int64)time(NULL));
...
186
}
175
}
187
  
176
  
188
static int l_stash_incr(lua_State *L) {
177
static int l_stash_incr(lua_State *L) {
  
178
	check_init(L);
189
	const char *key = luaL_checkstring(L, 1);
179
	const char *key = luaL_checkstring(L, 1);
190
	double amount = luaL_optnumber(L, 2, 1);
180
	double amount = luaL_optnumber(L, 2, 1);
191
	MemDB *mdb = &mem_dbs[current_db_idx];
  
192
	if (!mdb->db) {
  
193
		return luaL_error(L, "database not initialized");
  
194
	}
  
195
  
181
  
196
	sqlite3_stmt *stmt;
182
	sqlite3_stmt *stmt;
197
	const char *get_sql = "SELECT v, e FROM kv WHERE k = ?;";
183
	const char *get_sql = "SELECT v, e FROM kv WHERE k = ?;";
198
	if (sqlite3_prepare_v2(mdb->db, get_sql, -1, &stmt, NULL) != SQLITE_OK) {
184
	if (sqlite3_prepare_v2(mdb.db, get_sql, -1, &stmt, NULL) != SQLITE_OK) {
199
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
185
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
200
	}
186
	}
201
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
187
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
202
  
188
  
...
216
	snprintf(val_buf, sizeof(val_buf), "%g", new_val);
202
	snprintf(val_buf, sizeof(val_buf), "%g", new_val);
217
  
203
  
218
	const char *set_sql = "INSERT OR REPLACE INTO kv (k, v, e) VALUES (?, ?, ?);";
204
	const char *set_sql = "INSERT OR REPLACE INTO kv (k, v, e) VALUES (?, ?, ?);";
219
	if (sqlite3_prepare_v2(mdb->db, set_sql, -1, &stmt, NULL) != SQLITE_OK) {
205
	if (sqlite3_prepare_v2(mdb.db, set_sql, -1, &stmt, NULL) != SQLITE_OK) {
220
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
206
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
221
	}
207
	}
222
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
208
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
223
	sqlite3_bind_text(stmt, 2, val_buf, -1, SQLITE_TRANSIENT);
209
	sqlite3_bind_text(stmt, 2, val_buf, -1, SQLITE_TRANSIENT);
...
237
}
223
}
238
  
224
  
239
static int l_stash_keys(lua_State *L) {
225
static int l_stash_keys(lua_State *L) {
  
226
	check_init(L);
240
	const char *pattern = luaL_optstring(L, 1, "%");
227
	const char *pattern = luaL_optstring(L, 1, "%");
241
	MemDB *mdb = &mem_dbs[current_db_idx];
  
242
	if (!mdb->db) {
  
243
		return luaL_error(L, "database not initialized");
  
244
	}
  
245
  
228
  
246
	sqlite3_stmt *stmt;
229
	sqlite3_stmt *stmt;
247
	const char *sql = "SELECT k FROM kv WHERE k LIKE ? AND (e > ? OR e = 0);";
230
	const char *sql = "SELECT k FROM kv WHERE k LIKE ? AND (e > ? OR e = 0);";
248
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
231
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
249
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
232
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
250
	}
233
	}
251
	sqlite3_bind_text(stmt, 1, pattern, -1, SQLITE_TRANSIENT);
234
	sqlite3_bind_text(stmt, 1, pattern, -1, SQLITE_TRANSIENT);
252
	sqlite3_bind_int64(stmt, 2, (sqlite3_int64)time(NULL));
235
	sqlite3_bind_int64(stmt, 2, (sqlite3_int64)time(NULL));
...
262
}
245
}
263
  
246
  
264
static int l_stash_ttl(lua_State *L) {
247
static int l_stash_ttl(lua_State *L) {
  
248
	check_init(L);
265
	const char *key = luaL_checkstring(L, 1);
249
	const char *key = luaL_checkstring(L, 1);
266
	MemDB *mdb = &mem_dbs[current_db_idx];
  
267
	if (!mdb->db) {
  
268
		return luaL_error(L, "database not initialized");
  
269
	}
  
270
  
250
  
271
	sqlite3_stmt *stmt;
251
	sqlite3_stmt *stmt;
272
	const char *sql = "SELECT e FROM kv WHERE k = ? AND (e > ? OR e = 0);";
252
	const char *sql = "SELECT e FROM kv WHERE k = ? AND (e > ? OR e = 0);";
273
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
253
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
274
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
254
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
275
	}
255
	}
276
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
256
	sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
277
	sqlite3_bind_int64(stmt, 2, (sqlite3_int64)time(NULL));
257
	sqlite3_bind_int64(stmt, 2, (sqlite3_int64)time(NULL));
...
291
}
271
}
292
  
272
  
293
static int l_stash_expire(lua_State *L) {
273
static int l_stash_expire(lua_State *L) {
  
274
	check_init(L);
294
	const char *key = luaL_checkstring(L, 1);
275
	const char *key = luaL_checkstring(L, 1);
295
	int ttl = (int)luaL_checkinteger(L, 2);
276
	int ttl = (int)luaL_checkinteger(L, 2);
296
	MemDB *mdb = &mem_dbs[current_db_idx];
  
297
	if (!mdb->db) {
  
298
		return luaL_error(L, "database not initialized");
  
299
	}
  
300
  
277
  
301
	sqlite3_stmt *stmt;
278
	sqlite3_stmt *stmt;
302
	const char *sql = "UPDATE kv SET e = ? WHERE k = ? AND (e > ? OR e = 0);";
279
	const char *sql = "UPDATE kv SET e = ? WHERE k = ? AND (e > ? OR e = 0);";
303
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
280
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
304
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
281
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
305
	}
282
	}
306
  
283
  
307
	sqlite3_int64 expiry = (ttl > 0) ? (time(NULL) + ttl) : 0;
284
	sqlite3_int64 expiry = (ttl > 0) ? (time(NULL) + ttl) : 0;
...
310
	sqlite3_bind_int64(stmt, 3, (sqlite3_int64)time(NULL));
287
	sqlite3_bind_int64(stmt, 3, (sqlite3_int64)time(NULL));
311
  
288
  
312
	sqlite3_step(stmt);
289
	sqlite3_step(stmt);
313
	int changes = sqlite3_changes(mdb->db);
290
	int changes = sqlite3_changes(mdb.db);
314
	sqlite3_finalize(stmt);
291
	sqlite3_finalize(stmt);
315
  
292
  
316
	lua_pushboolean(L, changes > 0);
293
	lua_pushboolean(L, changes > 0);
...
318
}
295
}
319
  
296
  
320
static int l_stash_clear(lua_State *L) {
297
static int l_stash_clear(lua_State *L) {
321
	MemDB *mdb = &mem_dbs[current_db_idx];
298
	check_init(L);
322
	if (!mdb->db) {
299
	sqlite3_exec(mdb.db, "DELETE FROM kv;", NULL, NULL, NULL);
323
		return luaL_error(L, "database not initialized");
  
324
	}
  
325
  
  
326
	sqlite3_exec(mdb->db, "DELETE FROM kv;", NULL, NULL, NULL);
  
327
	return 0;
300
	return 0;
328
}
301
}
329
  
302
  
330
static int l_stash_count(lua_State *L) {
303
static int l_stash_count(lua_State *L) {
331
	MemDB *mdb = &mem_dbs[current_db_idx];
304
	check_init(L);
332
	if (!mdb->db) {
  
333
		return luaL_error(L, "database not initialized");
  
334
	}
  
335
  
  
336
	sqlite3_stmt *stmt;
305
	sqlite3_stmt *stmt;
337
	const char *sql = "SELECT COUNT(*) FROM kv WHERE (e > ? OR e = 0);";
306
	const char *sql = "SELECT COUNT(*) FROM kv WHERE (e > ? OR e = 0);";
338
	if (sqlite3_prepare_v2(mdb->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
307
	if (sqlite3_prepare_v2(mdb.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
339
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb->db));
308
		return luaL_error(L, "failed to prepare statement: %s", sqlite3_errmsg(mdb.db));
340
	}
309
	}
341
	sqlite3_bind_int64(stmt, 1, (sqlite3_int64)time(NULL));
310
	sqlite3_bind_int64(stmt, 1, (sqlite3_int64)time(NULL));
342
  
311
  
...
350
}
319
}
351
  
320
  
352
static const struct luaL_Reg stash_lib[] = {
321
static const struct luaL_Reg stash_lib[] = {
353
	{"use", l_stash_use},
322
	{"cleanup", l_stash_cleanup},
354
	{"set", l_stash_set},
323
	{"set", l_stash_set},
355
	{"get", l_stash_get},
324
	{"get", l_stash_get},
356
	{"del", l_stash_del},
325
	{"del", l_stash_del},
...