diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index 7f456bb..e2633ef 100755 Binary files a/embed/sqlite3.wasm and b/embed/sqlite3.wasm differ diff --git a/sqlite3/date.patch b/sqlite3/date.patch new file mode 100644 index 0000000..4b29c69 --- /dev/null +++ b/sqlite3/date.patch @@ -0,0 +1,57 @@ +--- sqlite3.c.orig ++++ sqlite3.c +@@ -340,6 +340,7 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ + p->iJD = sqlite3StmtCurrentTime(context); + if( p->iJD>0 ){ + p->validJD = 1; ++ p->tzSet = 1; + return 0; + }else{ + return 1; +@@ -355,6 +356,7 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ + static void setRawDateNumber(DateTime *p, double r){ + p->s = r; + p->rawS = 1; ++ p->tzSet = 1; + if( r>=0.0 && r<5373484.5 ){ + p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); + p->validJD = 1; +@@ -572,6 +574,7 @@ static int toLocaltime( + time_t t; + struct tm sLocal; + int iYearDiff; ++ DateTime x; + + /* Initialize the contents of sLocal to avoid a compiler warning. */ + memset(&sLocal, 0, sizeof(sLocal)); +@@ -585,7 +588,7 @@ static int toLocaltime( + ** SQLite attempts to map the year into an equivalent year within this + ** range, do the calculation, then map the year back. + */ +- DateTime x = *p; ++ x = *p; + computeYMD_HMS(&x); + iYearDiff = (2000 + x.Y%4) - x.Y; + x.Y += iYearDiff; +@@ -610,8 +613,13 @@ static int toLocaltime( + p->validHMS = 1; + p->validJD = 0; + p->rawS = 0; ++ p->tzSet = 0; + p->validTZ = 0; + p->isError = 0; ++ x = *p; ++ computeJD(&x); ++ p->tz = (x.iJD-p->iJD)/60000; ++ if( abs(p->tz)>= 900 ) p->tz = 0; + return SQLITE_OK; + } + #endif /* SQLITE_OMIT_LOCALTIME */ +@@ -781,6 +789,7 @@ static int parseModifier( + p->validJD = 1; + p->tzSet = 1; + } ++ p->tz = 0; + rc = SQLITE_OK; + } + #endif diff --git a/sqlite3/func.c b/sqlite3/func.c index f6e54f7..550e165 100644 --- a/sqlite3/func.c +++ b/sqlite3/func.c @@ -18,14 +18,15 @@ int sqlite3_create_collation_go(sqlite3 *db, const char *zName, void *pApp) { int sqlite3_create_function_go(sqlite3 *db, const char *zName, int nArg, int flags, void *pApp) { return sqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8 | flags, pApp, - go_func, NULL, NULL, go_destroy); + go_func, /*step=*/NULL, /*final=*/NULL, + go_destroy); } int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *zName, int nArg, int flags, void *pApp) { return sqlite3_create_window_function(db, zName, nArg, SQLITE_UTF8 | flags, - pApp, go_step, go_final, NULL, NULL, - go_destroy); + pApp, go_step, go_final, /*value=*/NULL, + /*inverse=*/NULL, go_destroy); } int sqlite3_create_window_function_go(sqlite3 *db, const char *zName, int nArg, diff --git a/sqlite3/progress.c b/sqlite3/progress.c index 8049ecb..781b1a3 100644 --- a/sqlite3/progress.c +++ b/sqlite3/progress.c @@ -5,5 +5,5 @@ int go_progress(void *); void sqlite3_progress_handler_go(sqlite3 *db, int n) { - sqlite3_progress_handler(db, n, go_progress, NULL); + sqlite3_progress_handler(db, n, go_progress, /*arg=*/NULL); } diff --git a/sqlite3/sqlite_cfg.h b/sqlite3/sqlite_cfg.h index c70982c..e40a8e3 100644 --- a/sqlite3/sqlite_cfg.h +++ b/sqlite3/sqlite_cfg.h @@ -5,12 +5,28 @@ #define SQLITE_OS_OTHER 1 #define SQLITE_BYTEORDER 1234 +#define HAVE_INT8_T 1 +#define HAVE_INT16_T 1 +#define HAVE_INT32_T 1 +#define HAVE_INT64_T 1 +#define HAVE_UINT8_T 1 +#define HAVE_UINT16_T 1 +#define HAVE_UINT32_T 1 +#define HAVE_UINT64_T 1 #define HAVE_STDINT_H 1 #define HAVE_INTTYPES_H 1 +#define HAVE_LOG2 1 +#define HAVE_LOG10 1 #define HAVE_ISNAN 1 + #define HAVE_USLEEP 1 +#define HAVE_NANOSLEEP 1 + +#define HAVE_GMTIME_R 1 #define HAVE_LOCALTIME_S 1 + +#define HAVE_MALLOC_H 1 #define HAVE_MALLOC_USABLE_SIZE 1 // Recommended Options @@ -51,11 +67,11 @@ #define SQLITE_ENABLE_RTREE 1 #define SQLITE_ENABLE_GEOPOLY 1 +#define SQLITE_SOUNDEX + // Session Extension // #define SQLITE_ENABLE_SESSION // #define SQLITE_ENABLE_PREUPDATE_HOOK -#define SQLITE_SOUNDEX - // Implemented in vfs.c. int localtime_s(struct tm *const pTm, time_t const *const pTime); \ No newline at end of file diff --git a/sqlite3/time.c b/sqlite3/time.c index 75b8744..890c4aa 100644 --- a/sqlite3/time.c +++ b/sqlite3/time.c @@ -1,3 +1,4 @@ +#include #include #include "sqlite3.h" @@ -26,7 +27,63 @@ static int time_collation(void *pArg, int nKey1, const void *pKey1, int nKey2, return rc; } +static void json_time_func(sqlite3_context *context, int argc, + sqlite3_value **argv) { + DateTime x; + if (isDate(context, argc, argv, &x)) return; + if (x.tzSet && x.tz) { + x.iJD += x.tz * 60000; + if (!validJulianDay(x.iJD)) return; + x.validYMD = 0; + x.validHMS = 0; + } + computeYMD_HMS(&x); + + sqlite3 *db = sqlite3_context_db_handle(context); + sqlite3_str *res = sqlite3_str_new(db); + + sqlite3_str_appendf(res, "%04d-%02d-%02dT%02d:%02d:%02d", // + x.Y, x.M, x.D, // + x.h, x.m, (int)(x.iJD / 1000 % 60)); + + if (x.useSubsec) { + int rem = x.iJD % 1000; + if (rem) { + sqlite3_str_appendchar(res, 1, '.'); + sqlite3_str_appendchar(res, 1, '0' + rem / 100); + if ((rem %= 100)) { + sqlite3_str_appendchar(res, 1, '0' + rem / 10); + if ((rem %= 10)) { + sqlite3_str_appendchar(res, 1, '0' + rem); + } + } + } + } + + if (x.tz) { + sqlite3_str_appendf(res, "%+03d:%02d", x.tz / 60, abs(x.tz) % 60); + } else { + sqlite3_str_appendchar(res, 1, 'Z'); + } + + int rc = sqlite3_str_errcode(res); + if (rc) { + sqlite3_result_error_code(context, rc); + return; + } + + int n = sqlite3_str_length(res); + sqlite3_result_text(context, sqlite3_str_finish(res), n, sqlite3_free); +} + int sqlite3_time_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) { - return sqlite3_create_collation(db, "time", SQLITE_UTF8, 0, time_collation); + sqlite3_create_collation_v2(db, "time", SQLITE_UTF8, /*arg=*/NULL, + time_collation, + /*destroy=*/NULL); + sqlite3_create_function_v2( + db, "json_time", -1, + SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS, /*arg=*/NULL, + json_time_func, /*step=*/NULL, /*final=*/NULL, /*destroy=*/NULL); + return SQLITE_OK; } \ No newline at end of file diff --git a/tests/time_test.go b/tests/time_test.go index 0a8dbda..0b398df 100644 --- a/tests/time_test.go +++ b/tests/time_test.go @@ -1,6 +1,7 @@ package tests import ( + "fmt" "reflect" "testing" "time" @@ -205,3 +206,103 @@ func TestDB_timeCollation(t *testing.T) { t.Fatal(err) } } + +func TestDB_jsonTime(t *testing.T) { + t.Parallel() + + reference := time.Date(2013, 10, 7, 4, 23, 19, 120_000_000, time.FixedZone("", -4*3600)) + + db, err := driver.Open(":memory:", nil) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS times (tstamp)`) + if err != nil { + t.Fatal(err) + } + + _, err = db.Exec(`INSERT INTO times VALUES (?), (?), (?)`, + reference, + sqlite3.TimeFormatUnixFrac.Encode(reference), + sqlite3.TimeFormatJulianDay.Encode(reference)) + if err != nil { + t.Fatal(err) + } + + rows, err := db.Query(` + SELECT + json_time(tstamp, 'auto', 'subsec'), + json_time(tstamp, 'auto') + FROM times`) + if err != nil { + t.Fatal(err) + } + defer rows.Close() + + for rows.Next() { + var t0, t1 time.Time + rows.Scan(&t0, &t1) + if want := reference; !t0.Equal(want) { + t.Errorf("got %v, want %v", t0, want) + } + if want := reference.Truncate(time.Second); !t1.Equal(want) { + t.Errorf("got %v, want %v", t1, want) + } + } +} + +func TestDB_isoWeek(t *testing.T) { + t.Parallel() + + tests := []time.Time{ + time.Date(1977, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1977, 1, 2, 0, 0, 0, 0, time.UTC), + time.Date(1977, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1978, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1978, 1, 2, 0, 0, 0, 0, time.UTC), + time.Date(1978, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1979, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1979, 1, 2, 0, 0, 0, 0, time.UTC), + time.Date(1979, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1980, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1980, 12, 28, 0, 0, 0, 0, time.UTC), + time.Date(1980, 12, 29, 0, 0, 0, 0, time.UTC), + time.Date(1980, 12, 30, 0, 0, 0, 0, time.UTC), + time.Date(1980, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1981, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1981, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1982, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1982, 1, 2, 0, 0, 0, 0, time.UTC), + time.Date(1982, 1, 3, 0, 0, 0, 0, time.UTC), + } + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + stmt, _, err := db.Prepare(`SELECT strftime('%G-W%V-%u', ?)`) + if err != nil { + t.Fatal(err) + } + defer stmt.Close() + + for _, tm := range tests { + stmt.BindTime(1, tm, sqlite3.TimeFormatDefault) + if stmt.Step() { + y, w := tm.ISOWeek() + d := tm.Weekday() + if d == 0 { + d = 7 + } + want := fmt.Sprintf("%04d-W%02d-%d", y, w, d) + if got := stmt.ColumnText(0); got != want { + t.Errorf("got %q, want %q (%v)", got, want, tm) + } + } + stmt.Reset() + } +} diff --git a/vfs/tests/mptest/testdata/mptest.wasm.bz2 b/vfs/tests/mptest/testdata/mptest.wasm.bz2 index f22e2ed..b0061cb 100644 --- a/vfs/tests/mptest/testdata/mptest.wasm.bz2 +++ b/vfs/tests/mptest/testdata/mptest.wasm.bz2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c59231ce10786b45be958027d23cffc74894a00120b30c8d3accb26f4182b29a -size 509312 +oid sha256:25ccdde5ddab41213abda7ecf6dd95c2966acaef5a645f9f35c170a84697556e +size 509445 diff --git a/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 b/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 index 31601d9..a776a2e 100644 --- a/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 +++ b/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f715bad486eeae35ecb3cf05a2e6265fbfc24a2de0836bdc8fd760510ac1d3a -size 524127 +oid sha256:4306d2a8c460f24955bb944923837800c887ee4080d725777392b834b7567bab +size 524003