package sqlite import ( "context" "database/sql" "embed" "errors" "fmt" "strings" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/sqlite" "github.com/golang-migrate/migrate/v4/source/iofs" "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" "go.unistack.org/micro/v4/logger" "git.unistack.org/unistack-org/pkgdash/internal/models" pb "git.unistack.org/unistack-org/pkgdash/proto" ) const ( pathMigration = `migrations/sqlite` ) type Sqlite struct { db *sql.DB fs embed.FS } func NewStorage() func(*sql.DB, embed.FS) interface{} { return func(db *sql.DB, fs embed.FS) interface{} { return &Sqlite{db: db, fs: fs} } } func (s *Sqlite) MigrateUp() error { driver, err := sqlite.WithInstance(s.db, &sqlite.Config{ MigrationsTable: sqlite.DefaultMigrationsTable, DatabaseName: "pkgdash", }) if err != nil { return err } source, err := iofs.New(s.fs, pathMigration) if err != nil { return err } // TODO: pass own logger m, err := migrate.NewWithInstance("fs", source, "pkgdash", driver) if err != nil { return err } if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { return err } return nil } func (s *Sqlite) MigrateDown() error { driver, err := sqlite.WithInstance(s.db, &sqlite.Config{ MigrationsTable: sqlite.DefaultMigrationsTable, DatabaseName: "pkgdash", }) if err != nil { return err } source, err := iofs.New(s.fs, pathMigration) if err != nil { return err } // TODO: pass own logger m, err := migrate.NewWithInstance("fs", source, "pkgdash", driver) if err != nil { return err } if err = m.Down(); err != nil && !errors.Is(err, migrate.ErrNoChange) { return err } return nil } func (s *Sqlite) PackagesUpdate(ctx context.Context, req *pb.PackagesUpdateReq) error { panic("need implement") } func (s *Sqlite) PackagesList(ctx context.Context, req *pb.PackagesListReq) ([]*models.Package, error) { var packages []*models.Package rows, err := s.db.QueryContext(ctx, queryPackagesList) if err != nil { return nil, err } for ; rows.Err() == nil; rows.Next() { pkg := &models.Package{} if err = rows.Scan( &pkg.ID, &pkg.Name, &pkg.URL, &pkg.Comments, ); err != nil { _ = rows.Close() return nil, err } packages = append(packages, pkg) } if err = rows.Err(); err != nil { return nil, err } if err = rows.Close(); err != nil { return nil, err } return packages, err } func (s *Sqlite) CommentsCreate(ctx context.Context, req *pb.CommentsCreateReq) (id uint64, err error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return 0, err } defer func() { if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { logger.Errorf(ctx, "AddComment: unable to rollback: %v", rollbackErr) } } else { err = tx.Commit() } }() if err = tx.QueryRowContext(ctx, queryCommentsCreate, req.Text, req.PackageId).Scan(&id); err != nil { return id, err } return id, err } func (s *Sqlite) PackagesCreate(ctx context.Context, req *pb.PackagesCreateReq) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } defer func() { if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { logger.Errorf(ctx, "AddPackage: unable to rollback: %v", rollbackErr) } } else { err = tx.Commit() } }() res, err := tx.ExecContext(ctx, queryPackagesCreate, req.Name, req.Url, pq.Array(req.Modules)) if err != nil { return err } if aff, affErr := res.RowsAffected(); err != nil { err = affErr } else if aff == 0 { err = errors.New("rows affected is 0") } return err } func (s *Sqlite) InsertButchModules(ctx context.Context, req []models.Module) ([]uint64, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return nil, err } defer func() { if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { logger.Errorf(ctx, "AddPackage: unable to rollback: %v", rollbackErr) } } else { err = tx.Commit() } }() query := generateQuery(req) rows, err := tx.QueryContext(ctx, query) if err != nil { return nil, err } defer func() { if err = rows.Close(); err != nil { return } err = rows.Err() }() result := make([]uint64, 0) for rows.Next() { tmp := uint64(0) if err = rows.Scan(&tmp); err != nil { return nil, err } result = append(result, tmp) } return result, err } func (s *Sqlite) GetModule(ctx context.Context, req *pb.ModulesListReq) ([]*models.Module, error) { var err error var modules []*models.Module rows, err := s.db.QueryContext(ctx, queryModulesList) if err != nil { return nil, err } defer func() { if err = rows.Close(); err != nil { return } err = rows.Err() }() for ; rows.Err() == nil; rows.Next() { mod := &models.Module{} if err = rows.Scan( &mod.ID, &mod.Name, &mod.Version, &mod.LastVersion, ); err != nil { return nil, err } modules = append(modules, mod) } if err = rows.Err(); err != nil { return nil, err } if err = rows.Close(); err != nil { return nil, err } return modules, nil } func (s *Sqlite) CommentsList(ctx context.Context, req *pb.CommentsListReq) ([]*models.Comment, error) { var comments []*models.Comment rows, err := s.db.QueryContext(ctx, queryCommentsList, req.PackageId) if err != nil { return nil, err } defer func() { if err = rows.Close(); err != nil { return } err = rows.Err() }() for ; rows.Err() == nil; rows.Next() { com := &models.Comment{} if err = rows.Scan( &com.ID, &com.Text, &com.Created, &com.Updated, ); err != nil { _ = rows.Close() return nil, err } comments = append(comments, com) } if err = rows.Err(); err != nil { return nil, err } if err = rows.Close(); err != nil { return nil, err } return comments, nil } func convertSliceUInt(arg ...uint64) []interface{} { result := make([]interface{}, 0, len(arg)) for i := range arg { result = append(result, arg[i]) } return result } func generateQuery(rsp []models.Module) string { const pattern = `%c('%s', '%s', '%s')` build := strings.Builder{} comma := ' ' for i := range rsp { str := fmt.Sprintf(pattern, comma, rsp[i].Name, rsp[i].Version, rsp[i].LastVersion) build.WriteString(str) comma = ',' } return fmt.Sprintf(queryInsMsgGetIDs, build.String()) } func generateArrayIneq(count int) string { return "(?" + strings.Repeat(",?", count-1) + ")" }