SQL json_time function.

This commit is contained in:
Nuno Cruces
2023-10-29 11:28:29 +00:00
parent 2157d0f325
commit 0bcdb712ba
9 changed files with 243 additions and 11 deletions

Binary file not shown.

57
sqlite3/date.patch Normal file
View File

@@ -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

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -1,3 +1,4 @@
#include <stddef.h>
#include <string.h>
#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;
}

View File

@@ -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()
}
}

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c59231ce10786b45be958027d23cffc74894a00120b30c8d3accb26f4182b29a
size 509312
oid sha256:25ccdde5ddab41213abda7ecf6dd95c2966acaef5a645f9f35c170a84697556e
size 509445

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9f715bad486eeae35ecb3cf05a2e6265fbfc24a2de0836bdc8fd760510ac1d3a
size 524127
oid sha256:4306d2a8c460f24955bb944923837800c887ee4080d725777392b834b7567bab
size 524003