grrr i can cli insert select but neither sqlite lib does it
parent
1fa8815026
commit
b088241a51
11
go.mod
11
go.mod
|
|
@ -4,16 +4,23 @@ go 1.23.0
|
|||
|
||||
toolchain go1.23.9
|
||||
|
||||
require modernc.org/sqlite v1.37.1
|
||||
require (
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/google/uuid v1.6.0
|
||||
modernc.org/sqlite v1.37.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
gorm.io/gorm v1.25.7 // indirect
|
||||
modernc.org/libc v1.65.7 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -1,9 +1,17 @@
|
|||
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/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
|
|
@ -21,6 +29,8 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
|
|
|
|||
136
main.go
136
main.go
|
|
@ -11,7 +11,8 @@ import (
|
|||
"slices"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
_ "github.com/glebarez/sqlite"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -27,51 +28,65 @@ func main() {
|
|||
|
||||
workd, err := ioutil.TempDir(os.TempDir(), "jellyfin-user-clone.*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
{
|
||||
if err := cp(path.Join(data, jellyDB), path.Join(workd, jellyDB)); err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
} else if err := cp(path.Join(data, libraryDB), path.Join(workd, libraryDB)); err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
jellyfinDB, err := sql.Open("sqlite", path.Join(workd, jellyDB))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
defer jellyfinDB.Close()
|
||||
|
||||
fromUUID, err := SelectOne[string](jellyfinDB, `SELECT Id FROM Users WHERE Username = $1`, fromU)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
fromID, err := SelectOne[int](jellyfinDB, `SELECT InternalId FROM Users WHERE Id = $1`, fromUUID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
log.Println(fromU, fromUUID, fromID)
|
||||
|
||||
if n, err := SelectOne[int](jellyfinDB, `SELECT COUNT(*) FROM Users WHERE Username = $1`, toU); err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
} else if n == 1 {
|
||||
} else if err := Exec(jellyfinDB, `INSERT INTO Users () SELECT * FROM Users WHERE Id = $1`, fromUUID); err != nil {
|
||||
panic(err)
|
||||
} else if err := CloneForColumn(jellyfinDB, `Users`, `Username`, fromU, toU, `Id`, `InternalId`); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
} else if n, err := SelectOne[int](jellyfinDB, `SELECT COUNT(*) FROM Users WHERE Username = $1`, toU); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
} else if n != 1 {
|
||||
log.Fatalf("still no username=%q after insert", toU)
|
||||
}
|
||||
|
||||
panic("not impl get toU, toUUID")
|
||||
toUUID, err := SelectOne[string](jellyfinDB, `SELECT Id FROM Users WHERE Username = $1`, toU)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
toID, err := SelectOne[int](jellyfinDB, `SELECT InternalId FROM Users WHERE Id = $1`, toUUID)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
log.Println(toU, toUUID, toID)
|
||||
|
||||
log.Fatalf("not impl get toU, toUUID")
|
||||
|
||||
tables, err := Tables(jellyfinDB)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
columns, err := Columns(jellyfinDB, table)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
log.Println(table, columns)
|
||||
|
||||
|
|
@ -87,24 +102,88 @@ func main() {
|
|||
|
||||
for _, column := range userColumns {
|
||||
if n, err := SelectOne[int](jellyfinDB, fmt.Sprintf(`SELECT COUNT(*) FROM %q WHERE %q = $1`, table, column), fromID); err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
} else if n > 0 {
|
||||
notThisColumn := strings.Join(slices.DeleteFunc(slices.Clone(columns), func(s string) bool { return s == column }), ", ")
|
||||
if err := Exec(jellyfinDB, fmt.Sprintf(`INSERT INTO %q (%q, %s) SELECT $2, %s FROM %q WHERE %q = $1`, table, column, notThisColumn, notThisColumn, table, column), fromID, toID); err != nil {
|
||||
panic(err)
|
||||
if err := CloneForColumn(jellyfinDB, table, column, fromID, toID); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
} else if n, err := SelectOne[int](jellyfinDB, fmt.Sprintf(`SELECT COUNT(*) FROM %q WHERE %q = $1`, table, column), fromUUID); err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%v", err)
|
||||
} else if n > 0 {
|
||||
panic("not impl: col is uuid")
|
||||
if err := CloneForColumn(jellyfinDB, table, column, fromUUID, toUUID); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(table, userColumns)
|
||||
panic("not impl")
|
||||
log.Fatalf("not impl")
|
||||
}
|
||||
}
|
||||
|
||||
func CloneForColumn[T any](db *sql.DB, table, column string, from, to T, omit ...string) error {
|
||||
columns, err := Columns(db, table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO SELECT idx_name FROM PRAGMA_INDEX_LIST('Users') WHERE "unique" to get unique constraints
|
||||
// TODO SELECT col_name FROM PRAGMA_INDEX_INFO(idx_name);
|
||||
// TODO how is .Users.InternalId maintained? by chance?
|
||||
|
||||
notTheseColumns := slices.DeleteFunc(slices.Clone(columns), func(s string) bool { return s == column || slices.Contains(omit, s) })
|
||||
for i := range notTheseColumns {
|
||||
notTheseColumns[i] = fmt.Sprintf("%q", notTheseColumns[i])
|
||||
}
|
||||
|
||||
notNullTextColumns, err := NotNullTextColumns(db, table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uuidGenColumns := slices.DeleteFunc(notNullTextColumns, func(s string) bool { return !slices.Contains(omit, s) })
|
||||
for i := range uuidGenColumns {
|
||||
uuidGenColumns[i] = fmt.Sprintf("%q", uuidGenColumns[i])
|
||||
}
|
||||
|
||||
notNullIntColumns, err := NotNullIntColumns(db, table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
incrGenColumns := slices.DeleteFunc(notNullIntColumns, func(s string) bool { return !slices.Contains(omit, s) })
|
||||
for i := range incrGenColumns {
|
||||
incrGenColumns[i] = fmt.Sprintf("%q", incrGenColumns[i])
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(
|
||||
`INSERT INTO %q (%q, %s) SELECT $2, %s %s %s FROM %q WHERE %q = $1`,
|
||||
table, column, strings.Join(append(incrGenColumns, append(uuidGenColumns, notTheseColumns...)...), ", "),
|
||||
func() string {
|
||||
incrs := make([]string, len(incrGenColumns))
|
||||
for i := range incrs {
|
||||
incrs[i] = fmt.Sprintf(`%s+100`, incrGenColumns[i])
|
||||
}
|
||||
s := strings.Join(incrs, ", ")
|
||||
if len(incrs) > 0 {
|
||||
s += ", "
|
||||
}
|
||||
return s
|
||||
}(),
|
||||
func() string {
|
||||
uuids := make([]string, len(uuidGenColumns))
|
||||
for i := range uuids {
|
||||
uuids[i] = fmt.Sprintf("'%s'", strings.ToUpper(uuid.New().String()))
|
||||
}
|
||||
s := strings.Join(uuids, ", ")
|
||||
if len(uuids) > 0 {
|
||||
s += ", "
|
||||
}
|
||||
return s
|
||||
}(), strings.Join(notTheseColumns, ", "), table, column,
|
||||
)
|
||||
log.Printf("EXEC | %s (%v, %v)", q, from, to)
|
||||
return Insert(db, q, to, from)
|
||||
}
|
||||
|
||||
func cp(from, to string) error {
|
||||
fromF, err := os.Open(from)
|
||||
if err != nil {
|
||||
|
|
@ -123,11 +202,24 @@ func cp(from, to string) error {
|
|||
}
|
||||
|
||||
func Tables(db *sql.DB) ([]string, error) {
|
||||
return Select[string](jellyfinDB, `SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`)
|
||||
return Select[string](db, `SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`)
|
||||
}
|
||||
|
||||
func Columns(db *sql.DB, table string) ([]string, error) {
|
||||
return Select[string](jellyfinDB, `SELECT name FROM PRAGMA_TABLE_INFO($1)`, table)
|
||||
return Select[string](db, `SELECT name FROM PRAGMA_TABLE_INFO($1)`, table)
|
||||
}
|
||||
|
||||
func NotNullTextColumns(db *sql.DB, table string) ([]string, error) {
|
||||
return Select[string](db, `SELECT name FROM PRAGMA_TABLE_INFO($1) WHERE "notnull" = 1 AND "type" = 'TEXT'`, table)
|
||||
}
|
||||
|
||||
func NotNullIntColumns(db *sql.DB, table string) ([]string, error) {
|
||||
return Select[string](db, `SELECT name FROM PRAGMA_TABLE_INFO($1) WHERE "notnull" = 1 AND "type" = 'INTEGER'`, table)
|
||||
}
|
||||
|
||||
func Insert(db *sql.DB, q string, args ...any) error {
|
||||
_, err := db.Exec(q, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func Exec(db *sql.DB, q string, args ...any) error {
|
||||
|
|
|
|||
Loading…
Reference in New Issue