Closed lukasschwab closed 9 months ago
Thanks for bringing this up @lukasschwab. Could you please share the exact CREATE TABLE texts SQL statement? A snippet of code that's executable would me greatly appreciated too :).
@haaawk here's a simplified self-contained demo of the issue! Note that you'll have to supply a valid .db
file to sql.Open
.
package main
import (
"database/sql"
"encoding/json"
"log"
"time"
_ "github.com/libsql/libsql-client-go/libsql"
_ "modernc.org/sqlite"
)
const (
createTableQuery = `CREATE TABLE IF NOT EXISTS texts (
id varchar(8) NOT NULL UNIQUE,
timestamp DATETIME NOT NULL
);`
upsertTextQuery = `
REPLACE INTO texts (id, timestamp)
VALUES (:id, :timestamp)
RETURNING id, timestamp;
`
)
func main() {
// Initialize timestamps.
var tWithLocation time.Time
t0json := []byte(`"2023-06-07T14:04:17.898834-07:00"`)
if err := json.Unmarshal([]byte(t0json), &tWithLocation); err != nil {
log.Fatalf("failed unmarshaling: %v", err)
}
var tWithoutLocation time.Time
t1json := []byte(`"2023-05-13T14:55:39.87727-04:00"`)
if err := json.Unmarshal([]byte(t1json), &tWithoutLocation); err != nil {
log.Fatalf("failed unmarshaling: %v", err)
}
log.Printf("Unmarshaled timestamps %v and %v", tWithLocation, tWithoutLocation)
// Initialize DB.
db, err := sql.Open("libsql", "file:///Users/lukas/Programming/tiir/tempstore.db")
if err != nil {
log.Fatalf("error opening DB connection: %v", err)
}
if _, err := db.Exec(createTableQuery); err != nil {
log.Fatalf("failed creating table: %v", err)
}
// Upserts.
var discardId = ""
if err := db.
QueryRow(upsertTextQuery, sql.Named("id", "aaaaaaaa"), sql.Named("timestamp", tWithLocation)).
Scan(&discardId, &time.Time{}); err != nil {
log.Fatalf("failed parsing timestamp *with* location: %v", err)
}
if err := db.
QueryRow(upsertTextQuery, sql.Named("id", "bbbbbbbb"), sql.Named("timestamp", tWithoutLocation)).
Scan(&discardId, &time.Time{}); err != nil {
log.Fatalf("failed parsing timestamp *without* location: %v", err)
}
}
Running it yields:
$ : > tempstore.db; go run ./cmd/issue
2023/10/02 09:23:31 Unmarshaled timestamps 2023-06-07 14:04:17.898834 -0700 PDT and 2023-05-13 14:55:39.87727 -0400 -0400
2023/10/02 09:23:31 failed parsing timestamp *without* location: sql: Scan error on column index 1, name "timestamp": unsupported Scan, storing driver.Value type string into type *time.Time
exit status 1
Expected behavior: program shouldn't fail.
This is an issue of "modernc.org/sqlite" driver. When I used "github.com/mattn/go-sqlite3" The example worked. Closing as this is not libsql related. Just an issue with one of the external sqlite drivers.
Hello! Had a great time working with this driver today. Ran into one issue with timestamps.
Timestamps without zone names
Expected behavior
A timestamp without a zone name — but with a valid offset — should round-trip to libsql without error and without adding an inaccurate zone name.
Reproduce
Context
Apologies — this is definitely not minimal, but I hope they're clear enough.
I'm migrating a bunch of records (
text
s) from a JSON file into a libsql database. Each text has a fieldtext.Timestamp
. There isn't a ton of data, so I parse the whole JSON file into memory, then upsert row-by-row into a local.db
file.These timestamps are formatted consistently in JSON, with the same timezone offset information — none explicitly specify a location:
For some reason, the default Go JSON parsing yields TZ location information for the
-07:00
timestamps but not for the-04:00
timestamps. That's odd, but I don't think it should be significant: both time zones have valid offsets.Write/read
I write these rows and capture the resulting record in a single
REPLACE INTO
query:...providing the
:id
and:timestamp
as named arguments (code paraphrased):That yields the following error, but only for the
-0400
timestamps for which Go didn't parse location information:The writes succeeded; only the scan failed. If I access the DB directly with
sqlite3
, I note different timestamp representations for timestamps with/without location info:Mitigation
For the purposes of my migration, I can make this work by coercing the bad timestamps into a named zone:
Arguably I should really normalize this DB data so that everything is in UTC... but this is a personal project where having a little bit of location info in the TZ is nice.
There might also be a solution with a custom
sql.Scanner
.