grrr i can cli insert select but neither sqlite lib does it

This commit is contained in:
Bel LaPointe
2025-06-04 09:03:47 -06:00
parent 1fa8815026
commit b088241a51
3 changed files with 133 additions and 24 deletions

136
main.go
View File

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