diff --git a/dialect_nosqlite_test.go b/dialect_nosqlite_test.go deleted file mode 100644 index 7a3ebeff..00000000 --- a/dialect_nosqlite_test.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !sqlite -// +build !sqlite - -package pop - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSqlite_NewDriver(t *testing.T) { - _, err := newSQLiteDriver() - require.Error(t, err) -} diff --git a/dialect_sqlite.go b/dialect_sqlite.go index fae270a6..367433be 100644 --- a/dialect_sqlite.go +++ b/dialect_sqlite.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "strings" "sync" "time" @@ -149,6 +150,7 @@ func (m *sqlite) SelectOne(c *Connection, model *Model, query Query) error { if err := genericSelectOne(c, model, query); err != nil { return fmt.Errorf("sqlite select one: %w", err) } + normalizeTimesToUTC(model.Value) return nil }) } @@ -158,6 +160,7 @@ func (m *sqlite) SelectMany(c *Connection, models *Model, query Query) error { if err := genericSelectMany(c, models, query); err != nil { return fmt.Errorf("sqlite select many: %w", err) } + normalizeTimesToUTC(models.Value) return nil }) } @@ -304,6 +307,11 @@ func urlParserSQLite3(cd *ConnectionDetails) error { return nil } + // Preserve the raw query string so finalizerSQLite can parse multi-value + // params (e.g. multiple _pragma entries) via url.Values, which supports + // duplicate keys. The generic withURL path sets RawOptions the same way. + cd.RawOptions = dbparts[1] + q, err := url.ParseQuery(dbparts[1]) if err != nil { return fmt.Errorf("unable to parse sqlite query: %w", err) @@ -316,28 +324,210 @@ func urlParserSQLite3(cd *ConnectionDetails) error { return nil } +// legacySQLiteParams maps mattn-style DSN params to SQLite pragma names for +// modernc.org/sqlite, which requires _pragma=name(value) syntax. Aliases +// (e.g. _foreign_keys/_fk) list the canonical long form first so the first +// match wins when both aliases are present in the same DSN. +var legacySQLiteParams = []struct{ key, pragma string }{ + {"_foreign_keys", "foreign_keys"}, + {"_fk", "foreign_keys"}, + {"_journal_mode", "journal_mode"}, + {"_journal", "journal_mode"}, + {"_busy_timeout", "busy_timeout"}, + {"_timeout", "busy_timeout"}, + {"_synchronous", "synchronous"}, + {"_sync", "synchronous"}, + {"_auto_vacuum", "auto_vacuum"}, + {"_vacuum", "auto_vacuum"}, + {"_case_sensitive_like", "case_sensitive_like"}, + {"_cslike", "case_sensitive_like"}, + {"_defer_foreign_keys", "defer_foreign_keys"}, + {"_defer_fk", "defer_foreign_keys"}, + {"_locking_mode", "locking_mode"}, + {"_locking", "locking_mode"}, + {"_recursive_triggers", "recursive_triggers"}, + {"_rt", "recursive_triggers"}, + {"_cache_size", "cache_size"}, + {"_ignore_check_constraints", "ignore_check_constraints"}, + {"_query_only", "query_only"}, + {"_secure_delete", "secure_delete"}, + {"_writable_schema", "writable_schema"}, +} + +// sqliteInternalKeys are pop-internal connection options that must not be +// forwarded to the SQLite DSN. +var sqliteInternalKeys = map[string]bool{ + "migration_table_name": true, + "retry_sleep": true, + "retry_limit": true, + "lock": true, +} + +// moderncSQLiteParams is the complete set of DSN query parameters recognised +// by modernc.org/sqlite. Any key not in this set will be warned and stripped. +// Source: modernc.org/sqlite@v1.47.0/sqlite.go applyQueryParams() and +// modernc.org/sqlite@v1.47.0/conn.go newConn(). +var moderncSQLiteParams = map[string]bool{ + "vfs": true, // VFS name + "_pragma": true, // PRAGMA name(value); repeatable + "_time_format": true, // time write format; only "sqlite" is valid + "_txlock": true, // transaction locking: deferred/immediate/exclusive + "_time_integer_format": true, // integer time repr: unix/unix_milli/unix_micro/unix_nano + "_inttotime": true, // convert integer columns to time.Time + "_texttotime": true, // affect ColumnTypeScanType for TEXT date columns +} + func finalizerSQLite(cd *ConnectionDetails) { - defs := map[string]string{ - "_busy_timeout": "5000", + // modernc.org/sqlite (registered as "sqlite3") requires pragmas via + // _pragma=name(value) DSN params. Legacy mattn-style params are silently + // ignored by modernc and must be translated. + + // Build url.Values from RawOptions (set when a DSN URL was parsed) or the + // Options map (set programmatically). url.Values supports duplicate keys, + // which is required for multiple _pragma entries. + var q url.Values + if cd.RawOptions != "" { + var err error + q, err = url.ParseQuery(cd.RawOptions) + if err != nil { + q = url.Values{} + } + } else { + q = url.Values{} + for k, v := range cd.Options { + if !sqliteInternalKeys[k] { + q.Set(k, v) + } + } } - forced := map[string]string{ - "_fk": "true", + + // _loc is a mattn-only timezone param with no modernc equivalent. + // modernc returns time.UTC natively; use _time_format if a different format is needed. + if q.Get("_loc") != "" { + log(logging.Warn, "SQLite DSN param \"_loc\" has no modernc.org/sqlite equivalent and will be ignored") + q.Del("_loc") + } + + // Translate all legacy mattn-style params to _pragma=name(value). + for _, p := range legacySQLiteParams { + if val := q.Get(p.key); val != "" { + q.Del(p.key) + if !sqlitePragmaSet(q, p.pragma) { + q.Add("_pragma", p.pragma+"("+val+")") + } + } } - for k, def := range defs { - cd.setOptionWithDefault(k, cd.option(k), def) + // Strip any remaining keys that modernc.org/sqlite does not recognise. + for k := range q { + if !moderncSQLiteParams[k] { + log(logging.Warn, "SQLite DSN param %q is not supported by modernc.org/sqlite and will be ignored", k) + q.Del(k) + delete(cd.Options, k) + } } - for k, v := range forced { - // respect user specified options but print warning! - cd.setOptionWithDefault(k, cd.option(k), v) - if cd.option(k) != v { // when user-defined option exists - log(logging.Warn, "IMPORTANT! '%s: %s' option is required to work properly but your current setting is '%v: %v'.", k, v, k, cd.option(k)) - log(logging.Warn, "It is highly recommended to remove '%v: %v' option from your config!", k, cd.option(k)) - } // or override with `cd.Options[k] = v`? - if cd.URL != "" && !strings.Contains(cd.URL, k+"="+v) { - log(logging.Warn, "IMPORTANT! '%s=%s' option is required to work properly. Please add it to the database URL in the config!", k, v) - } // or fix user specified url? + // Apply default busy_timeout if not configured. + if !sqlitePragmaSet(q, "busy_timeout") { + q.Add("_pragma", "busy_timeout(5000)") + } + // Enforce foreign_keys. + if !sqlitePragmaSet(q, "foreign_keys") { + q.Add("_pragma", "foreign_keys(1)") + if cd.URL != "" { + log(logging.Warn, "IMPORTANT! '_pragma=foreign_keys(1)' is required for correct operation. Add it to your SQLite DSN.") + } + } + cd.RawOptions = q.Encode() + + // Reflect all applied pragmas back into cd.Options for backward-compatible + // reads via cd.option(). sqliteOptionKey maps pragma names to their preferred + // option key; everything else uses "_"+pragmaName. + for _, pragma := range q["_pragma"] { + rawName, rawValue, ok := strings.Cut(pragma, "(") + if !ok { + continue + } + name := strings.ToLower(strings.TrimSpace(rawName)) + value := strings.TrimSuffix(strings.TrimSpace(rawValue), ")") + key := "_" + name + if name == "foreign_keys" { + key = "_fk" + } + cd.setOption(key, value) + } +} + +// sqlitePragmaSet reports whether q already contains a _pragma entry for +// pragmaName (case-insensitive). Requires the pragma name to be immediately +// followed by '(' to avoid false matches on names sharing a common prefix +// (e.g. "foreign_keys" vs "foreign_keys_per_table"). +func sqlitePragmaSet(q url.Values, pragmaName string) bool { + prefix := strings.ToLower(pragmaName) + "(" + for _, p := range q["_pragma"] { + if strings.HasPrefix(strings.ToLower(strings.TrimSpace(p)), prefix) { + return true + } + } + return false +} + +var ( + typeTime = reflect.TypeFor[time.Time]() + typeNullTime = reflect.TypeFor[sql.NullTime]() +) + +// normalizeTimesToUTC walks v (a pointer to a struct or pointer to a slice of +// structs) and calls .UTC() on every time.Time and valid sql.NullTime field, +// including those inside embedded structs and behind pointer fields. +// This is required because modernc.org/sqlite may return time.Time values +// whose Location pointer is not time.UTC even when the stored instant is UTC +// (e.g. unnamed FixedZone("", 0) from rows written by mattn/go-sqlite3). +func normalizeTimesToUTC(v any) { + normalizeValue(reflect.ValueOf(v)) +} + +func normalizeValue(rv reflect.Value) { + // Dereference any pointer indirection (handles nil safely). + for rv.Kind() == reflect.Pointer { + if rv.IsNil() { + return + } + rv = rv.Elem() + } + switch rv.Kind() { + case reflect.Slice, reflect.Array: + for i := range rv.Len() { + normalizeValue(rv.Index(i)) + } + case reflect.Struct: + for i := range rv.NumField() { + f := rv.Field(i) + if !f.CanSet() { + continue + } + // Dereference one pointer level so *time.Time and *Struct are + // handled the same as their value equivalents. + target := f + if target.Kind() == reflect.Pointer { + if target.IsNil() { + continue + } + target = target.Elem() + } + switch target.Type() { + case typeTime: + target.Set(reflect.ValueOf(target.Interface().(time.Time).UTC())) + case typeNullTime: + nt := target.Interface().(sql.NullTime) + if nt.Valid { + nt.Time = nt.Time.UTC() + target.Set(reflect.ValueOf(nt)) + } + default: + normalizeValue(target) + } + } } } diff --git a/dialect_sqlite_tag.go b/dialect_sqlite_tag.go index 286dd783..0a5386fc 100644 --- a/dialect_sqlite_tag.go +++ b/dialect_sqlite_tag.go @@ -1,8 +1,11 @@ -//go:build sqlite -// +build sqlite - package pop import ( - _ "github.com/mattn/go-sqlite3" // Load SQLite3 CGo driver + "database/sql" + + moderncsqlite "modernc.org/sqlite" // Load SQLite3 pure-Go driver ) + +func init() { + sql.Register("sqlite3", &moderncsqlite.Driver{}) +} diff --git a/dialect_sqlite_test.go b/dialect_sqlite_test.go index ac5db21a..0314c84c 100644 --- a/dialect_sqlite_test.go +++ b/dialect_sqlite_test.go @@ -1,17 +1,17 @@ -//go:build sqlite -// +build sqlite - package pop import ( + "database/sql" "fmt" + "net/url" "path/filepath" "testing" + "time" "github.com/stretchr/testify/require" ) -var sqliteDefaultOptions = map[string]string{"_busy_timeout": "5000", "_fk": "true"} +var sqliteDefaultOptions = map[string]string{"_busy_timeout": "5000", "_fk": "1"} func Test_ConnectionDetails_Finalize_SQLite_URL_Only(t *testing.T) { r := require.New(t) @@ -36,7 +36,7 @@ func Test_ConnectionDetails_Finalize_SQLite_OverrideOptions_URL_Only(t *testing. r.NoError(err) r.Equal("sqlite3", cd.Dialect, "given dialect: N/A") r.Equal("/tmp/foo.db", cd.Database, "given url: sqlite3:///tmp/foo.db?_fk=false&foo=bar") - r.EqualValues(map[string]string{"_fk": "false", "foo": "bar", "_busy_timeout": "5000"}, cd.Options, "given url: sqlite3:///tmp/foo.db?_fk=false&foo=bar") + r.EqualValues(map[string]string{"_fk": "false", "_busy_timeout": "5000"}, cd.Options, "given url: sqlite3:///tmp/foo.db?_fk=false&foo=bar") } func Test_ConnectionDetails_Finalize_SQLite_SynURL_Only(t *testing.T) { @@ -127,7 +127,7 @@ func Test_ConnectionDetails_Finalize_SQLite_OverrideOptions_Synonym_Path(t *test r.NoError(err) r.Equal("sqlite3", cd.Dialect, "given dialect: N/A") r.Equal("/tmp/foo.db", cd.Database, "given url: sqlite3:///tmp/foo.db") - r.EqualValues(map[string]string{"_fk": "false", "foo": "bar", "_busy_timeout": "5000"}, cd.Options, "given url: sqlite3:///tmp/foo.db?_fk=false&foo=bar") + r.EqualValues(map[string]string{"_fk": "false", "_busy_timeout": "5000"}, cd.Options, "given url: sqlite3:///tmp/foo.db?_fk=false&foo=bar") } func Test_ConnectionDetails_FinalizeOSPath(t *testing.T) { @@ -143,6 +143,31 @@ func Test_ConnectionDetails_FinalizeOSPath(t *testing.T) { r.EqualValues(p, cd.Database) } +func Test_ConnectionDetails_Finalize_SQLite_NoTimeFormatDefault(t *testing.T) { + t.Parallel() + // finalizerSQLite must NOT add _time_format=sqlite as a default. + // + // _time_format=sqlite maps to the write format "2006-01-02 15:04:05.999999999-07:00" + // (not timezone-free as the name implies). For a UTC time this produces + // "2024-06-15 10:30:00+00:00", which time.Parse reads back as + // FixedZone("", 0) — a broken, unnamed zero-offset zone distinct from time.UTC. + // + // Without any _time_format, modernc uses t.String() which includes the + // timezone name ("... +0000 UTC"). Go's time.Parse recognises "UTC" as the + // canonical time.UTC pointer, so no FixedZone workarounds are needed. + for _, url := range []string{ + "sqlite3:///tmp/foo.db", + "sqlite:///tmp/foo.db", + } { + t.Run(url, func(t *testing.T) { + cd := &ConnectionDetails{URL: url} + require.NoError(t, cd.Finalize()) + require.NotContains(t, cd.RawOptions, "_time_format", + "finalizerSQLite must not inject _time_format — doing so would break UTC round-trips") + }) + } +} + func TestSqlite_CreateDB(t *testing.T) { r := require.New(t) @@ -196,3 +221,277 @@ func TestSqlite_NewDriver(t *testing.T) { _, err := newSQLiteDriver() require.NoError(t, err) } + +func Test_normalizeTimesToUTC(t *testing.T) { + fixedZone := time.FixedZone("", 0) // unnamed zero-offset zone, distinct from time.UTC + local := time.Local + + now := time.Now().Truncate(time.Second) + utcNow := now.UTC() + fixedNow := now.In(fixedZone) + localNow := now.In(local) + + t.Run("time.Time fields normalized", func(t *testing.T) { + type row struct { + T time.Time + } + for _, input := range []time.Time{fixedNow, localNow, utcNow} { + r := &row{T: input} + normalizeTimesToUTC(r) + require.Equal(t, time.UTC, r.T.Location(), "Location must be time.UTC") + require.True(t, utcNow.Equal(r.T), "instant must be preserved") + } + }) + + t.Run("sql.NullTime valid normalized", func(t *testing.T) { + type row struct { + NT sql.NullTime + } + r := &row{NT: sql.NullTime{Time: fixedNow, Valid: true}} + normalizeTimesToUTC(r) + require.Equal(t, time.UTC, r.NT.Time.Location()) + require.True(t, utcNow.Equal(r.NT.Time)) + require.True(t, r.NT.Valid) + }) + + t.Run("sql.NullTime invalid untouched", func(t *testing.T) { + type row struct { + NT sql.NullTime + } + r := &row{NT: sql.NullTime{Valid: false}} + normalizeTimesToUTC(r) + require.False(t, r.NT.Valid) + require.True(t, r.NT.Time.IsZero()) + }) + + t.Run("embedded struct fields normalized", func(t *testing.T) { + // Exported embedded types (e.g. pop.Model, pop.Timestamps) are the + // real-world case; reflection's CanSet() returns false for unexported + // embedded fields so they cannot be walked. + type Inner struct{ CreatedAt time.Time } + type outer struct { + Inner + UpdatedAt time.Time + } + r := &outer{ + Inner: Inner{CreatedAt: fixedNow}, + UpdatedAt: localNow, + } + normalizeTimesToUTC(r) + require.Equal(t, time.UTC, r.CreatedAt.Location()) + require.Equal(t, time.UTC, r.UpdatedAt.Location()) + }) + + t.Run("slice of structs normalized", func(t *testing.T) { + type row struct{ T time.Time } + rows := []row{{fixedNow}, {localNow}, {utcNow}} + normalizeTimesToUTC(&rows) + for _, r := range rows { + require.Equal(t, time.UTC, r.T.Location()) + require.True(t, utcNow.Equal(r.T)) + } + }) + + t.Run("slice of pointer-to-struct normalized", func(t *testing.T) { + type row struct{ T time.Time } + rows := []*row{{fixedNow}, {localNow}} + normalizeTimesToUTC(&rows) + for _, r := range rows { + require.Equal(t, time.UTC, r.T.Location()) + } + }) + + t.Run("already UTC unchanged", func(t *testing.T) { + type row struct{ T time.Time } + r := &row{T: utcNow} + normalizeTimesToUTC(r) + require.Equal(t, time.UTC, r.T.Location()) + require.True(t, utcNow.Equal(r.T)) + }) + + t.Run("nil pointer no panic", func(t *testing.T) { + require.NotPanics(t, func() { + normalizeTimesToUTC((*struct{ T time.Time })(nil)) + }) + }) + + t.Run("*time.Time field normalized", func(t *testing.T) { + type row struct{ T *time.Time } + r := &row{T: &fixedNow} + normalizeTimesToUTC(r) + require.NotNil(t, r.T) + require.Equal(t, time.UTC, r.T.Location()) + require.True(t, utcNow.Equal(*r.T)) + }) + + t.Run("nil *time.Time field not panicking", func(t *testing.T) { + type row struct{ T *time.Time } + r := &row{T: nil} + require.NotPanics(t, func() { normalizeTimesToUTC(r) }) + require.Nil(t, r.T) + }) + + t.Run("pointer-to-struct with time fields", func(t *testing.T) { + type Inner struct{ CreatedAt time.Time } + type outer struct{ Details *Inner } + r := &outer{Details: &Inner{CreatedAt: fixedNow}} + normalizeTimesToUTC(r) + require.Equal(t, time.UTC, r.Details.CreatedAt.Location()) + require.True(t, utcNow.Equal(r.Details.CreatedAt)) + }) +} + +// parsePragmas returns the _pragma slice from cd.RawOptions. +func parsePragmas(t *testing.T, cd *ConnectionDetails) []string { + t.Helper() + q, err := url.ParseQuery(cd.RawOptions) + require.NoError(t, err) + return q["_pragma"] +} + +// Test_sqlitePragmaSet verifies that the pragma name must be followed by '(' +// to avoid false-positive prefix matches (e.g. foreign_keys_per_table). +func Test_sqlitePragmaSet(t *testing.T) { + tests := []struct { + name string + pragma string + values []string + wantResult bool + }{ + {"exact match", "foreign_keys", []string{"foreign_keys(1)"}, true}, + {"case insensitive", "foreign_keys", []string{"FOREIGN_KEYS(1)"}, true}, + {"leading whitespace", "foreign_keys", []string{" foreign_keys(1)"}, true}, + {"does NOT match longer name", "foreign_keys", []string{"foreign_keys_per_table(1)"}, false}, + {"match in multi-value slice", "busy_timeout", []string{"foreign_keys(1)", "busy_timeout(5000)"}, true}, + {"empty values", "foreign_keys", nil, false}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + q := url.Values{"_pragma": tc.values} + require.Equal(t, tc.wantResult, sqlitePragmaSet(q, tc.pragma)) + }) + } +} + +// Test_ConnectionDetails_Finalize_SQLite_RawOptions_Defaults asserts that +// finalizerSQLite injects defaults as _pragma entries and echoes them back. +func Test_ConnectionDetails_Finalize_SQLite_RawOptions_Defaults(t *testing.T) { + cd := &ConnectionDetails{URL: "sqlite3:///tmp/foo.db"} + require.NoError(t, cd.Finalize()) + + pragmas := parsePragmas(t, cd) + require.Contains(t, pragmas, "busy_timeout(5000)") + require.Contains(t, pragmas, "foreign_keys(1)") + + q, _ := url.ParseQuery(cd.RawOptions) + require.NotContains(t, q, "_fk") + require.NotContains(t, q, "_busy_timeout") + + require.Equal(t, "1", cd.Options["_fk"]) + require.Equal(t, "5000", cd.Options["_busy_timeout"]) +} + +// Test_ConnectionDetails_Finalize_SQLite_RawOptions_Override asserts that +// explicit legacy params translate correctly and unsupported params are stripped. +func Test_ConnectionDetails_Finalize_SQLite_RawOptions_Override(t *testing.T) { + cd := &ConnectionDetails{URL: "sqlite3:///tmp/foo.db?_fk=false&foo=bar"} + require.NoError(t, cd.Finalize()) + + pragmas := parsePragmas(t, cd) + require.Contains(t, pragmas, "foreign_keys(false)") + require.NotContains(t, pragmas, "foreign_keys(true)") + require.Contains(t, pragmas, "busy_timeout(5000)") + + q, _ := url.ParseQuery(cd.RawOptions) + require.NotContains(t, q, "foo", "unsupported params must be stripped") + require.NotContains(t, q, "_fk") + + require.Equal(t, "false", cd.Options["_fk"]) +} + +// Test_ConnectionDetails_Finalize_SQLite_LegacyParams covers the full set of +// mattn-compatible legacy params translated to _pragma entries. +func Test_ConnectionDetails_Finalize_SQLite_LegacyParams(t *testing.T) { + tests := []struct { + name string + param string + value string + pragma string + }{ + {"foreign_keys long form", "_foreign_keys", "0", "foreign_keys(0)"}, + {"foreign_keys alias", "_fk", "0", "foreign_keys(0)"}, + {"journal_mode", "_journal_mode", "WAL", "journal_mode(WAL)"}, + {"journal_mode alias", "_journal", "WAL", "journal_mode(WAL)"}, + {"busy_timeout", "_busy_timeout", "1000", "busy_timeout(1000)"}, + {"busy_timeout alias", "_timeout", "1000", "busy_timeout(1000)"}, + {"synchronous", "_synchronous", "NORMAL", "synchronous(NORMAL)"}, + {"synchronous alias", "_sync", "NORMAL", "synchronous(NORMAL)"}, + {"auto_vacuum", "_auto_vacuum", "FULL", "auto_vacuum(FULL)"}, + {"auto_vacuum alias", "_vacuum", "FULL", "auto_vacuum(FULL)"}, + {"locking_mode", "_locking_mode", "EXCLUSIVE", "locking_mode(EXCLUSIVE)"}, + {"secure_delete", "_secure_delete", "true", "secure_delete(true)"}, + {"recursive_triggers", "_recursive_triggers", "true", "recursive_triggers(true)"}, + {"query_only", "_query_only", "true", "query_only(true)"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cd := &ConnectionDetails{ + Dialect: "sqlite", + Database: "./foo.db", + Options: map[string]string{tc.param: tc.value}, + } + require.NoError(t, cd.Finalize()) + + pragmas := parsePragmas(t, cd) + require.Contains(t, pragmas, tc.pragma) + + q, _ := url.ParseQuery(cd.RawOptions) + require.NotContains(t, q, tc.param, "legacy param must be translated away from DSN") + }) + } +} + +// Test_ConnectionDetails_Finalize_SQLite_Programmatic tests the path where +// Options is set directly (no URL), verifying DSN and echo-back. +func Test_ConnectionDetails_Finalize_SQLite_Programmatic(t *testing.T) { + cd := &ConnectionDetails{ + Dialect: "sqlite", + Database: "./foo.db", + Options: map[string]string{ + "_journal_mode": "WAL", + "_busy_timeout": "10000", + }, + } + require.NoError(t, cd.Finalize()) + + pragmas := parsePragmas(t, cd) + require.Contains(t, pragmas, "journal_mode(WAL)") + require.Contains(t, pragmas, "busy_timeout(10000)") + require.Contains(t, pragmas, "foreign_keys(1)") + + q, _ := url.ParseQuery(cd.RawOptions) + require.NotContains(t, q, "_journal_mode") + require.NotContains(t, q, "_busy_timeout") + require.NotContains(t, q, "_fk") + + require.Equal(t, "WAL", cd.Options["_journal_mode"]) + require.Equal(t, "10000", cd.Options["_busy_timeout"]) + require.Equal(t, "1", cd.Options["_fk"]) +} + +// Test_ConnectionDetails_Finalize_SQLite_DirectPragma verifies that +// _pragma=name(value) entries set directly in the DSN survive and are echoed. +func Test_ConnectionDetails_Finalize_SQLite_DirectPragma(t *testing.T) { + cd := &ConnectionDetails{ + URL: "sqlite3:///tmp/foo.db?_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)", + } + require.NoError(t, cd.Finalize()) + + pragmas := parsePragmas(t, cd) + require.Contains(t, pragmas, "journal_mode(WAL)") + require.Contains(t, pragmas, "foreign_keys(1)") + require.NotContains(t, pragmas, "foreign_keys(true)") // default not added again + + require.Equal(t, "WAL", cd.Options["_journal_mode"]) + require.Equal(t, "1", cd.Options["_fk"]) +} diff --git a/go.mod b/go.mod index 378c1612..67b88070 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ory/pop/v6 -go 1.25 +go 1.25.0 require ( github.com/XSAM/otelsql v0.39.0 @@ -20,13 +20,13 @@ require ( github.com/jackc/pgx/v5 v5.7.5 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 - github.com/mattn/go-sqlite3 v1.14.32 github.com/spf13/cobra v1.10.1 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel/sdk v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 - golang.org/x/sync v0.16.0 + golang.org/x/sync v0.19.0 gopkg.in/yaml.v2 v2.4.0 + modernc.org/sqlite v1.47.0 ) require ( @@ -34,6 +34,7 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -56,8 +57,10 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -67,12 +70,15 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.70.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 06af2519..3b580c0c 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -61,11 +63,15 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1 github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -111,18 +117,21 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= -github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -172,25 +181,25 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -200,25 +209,25 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -231,3 +240,31 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk= +modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=