package queries import ( "fmt" "fiskerinc.com/modules/common" "fiskerinc.com/modules/common/carupdatestatus" "fiskerinc.com/modules/logger" "fiskerinc.com/modules/validator" "github.com/go-pg/pg/v10" "github.com/go-pg/pg/v10/orm" "github.com/pkg/errors" ) var FINAL_UPDATE_STATUS = carupdatestatus.FINAL_UPDATE_STATUS type CarUpdatesInterface interface { Count(filter *common.CarUpdate) (int, error) Delete(update *common.CarUpdate) (orm.Result, error) CountUpdateStatuses(carupdateid int64) (int, error) GetUpdateStatuses(carupdateid int64, paging *PageQueryOptions) ([]common.CarUpdateStatus, error) TruncateRequirementsAwaitForUpdate(carupdateid int64) (orm.Result, error) Insert(update *common.CarUpdate) (orm.Result, error) InsertAndCreateStatus(update *common.CarUpdate) (orm.Result, error) // Insert a car update and create the appropriate update_status entry Load(update *common.CarUpdate) error LogStatus(update *common.CarUpdate) (orm.Result, error) SelectByID(id int64) (*common.CarUpdate, error) SelectByManifestID(int64) ([]common.CarUpdate, error) SelectByVIN(vin string) ([]common.CarUpdate, error) SelectMostRecentByVINs(vins []string) ([]common.CarUpdate, error) SelectOrInsert(update *common.CarUpdate) (bool, error) Select(filter *common.CarUpdate, paging *PageQueryOptions) ([]common.CarUpdate, error) UpdateStatus(update *common.CarUpdate) (orm.Result, error) UpdateStatusIfNotRepeat(update *common.CarUpdate) (orm.Result, error) GetManifest(carupdateid int64) (*common.UpdateManifest, error) HasPendingUpdates(manifestID int64, vin string) (bool, error) HasPendingUpdatesFromAftersalesUser(manifestID int64, vin string) (updateID int64, pendingUpdateAftersales bool, err error) // Cancel any pending aftersales update InsertMissingFlashpack(vin string, flashpackVersion string) error // Flashpack Version = OS Version Number } // CarUpdate query methods type CarUpdates struct { QueryBase } func (c *CarUpdates) selectFilter(query *orm.Query, filter *common.CarUpdate) { if filter.ID > 0 { query.Where("car_update.id = ?", filter.ID) } if filter.VIN != "" { query.Where("vin = ?", filter.VIN) } if filter.UpdateManifestID > 0 { query.Where("update_manifest_id = ?", filter.UpdateManifestID) } if filter.Status != "" { query.Where("status = ?", filter.Status) } if filter.UpdateSource != "" { query.Where("update_source = ?", filter.UpdateSource) } } // StatusHistoryFilter get CarUpdates with certain status' filtered out. // When a status is filtered out, CarUpdates with that status will still // be returned, but will contain their last status not ignored. func StatusHistoryFilter(query *orm.Query, ignoreStatuses []string) { query.ColumnExpr("car_update.id") query.ColumnExpr("car_update.vin") query.ColumnExpr("car_update.created_at") query.ColumnExpr("car_update.updated_at") query.ColumnExpr("car_update.update_manifest_id") query.ColumnExpr("car_update.error_code") query.ColumnExpr("car_update.info") query.ColumnExpr("car_update.username") query.ColumnExpr("car_update_display_status.display_status AS status") query.Join(`LEFT JOIN car_update_display_status ON car_update.id = car_update_display_status.car_update_id`) } // CarUpdate select by car update id func (c *CarUpdates) SelectByID(id int64) (*common.CarUpdate, error) { update := common.CarUpdate{ID: id} err := c.GetDBConn().Model(&update).WherePK(). Relation("UpdateManifest"). Select() return &update, errors.WithStack(err) } // SelectByVIN returns list of car updates by VIN func (c *CarUpdates) SelectByVIN(vin string) ([]common.CarUpdate, error) { updates := []common.CarUpdate{} err := c.GetDBConn().Model(&updates).Where("vin = ?", vin). Relation("UpdateManifest"). Select() return updates, errors.WithStack(err) } // SelectMostRecentByVINs returns a list of most recent car update for VINs func (c *CarUpdates) SelectMostRecentByVINs(vins []string) ([]common.CarUpdate, error) { updates := []common.CarUpdate{} err := c.GetDBConn().Model(&updates). Where("vin IN (?)", pg.In(vins)). WhereGroup(func(q *pg.Query) (*pg.Query, error) { return q.Where("(car_update.created_at = (SELECT MAX(t2.created_at) FROM public.car_updates t2 WHERE t2.vin = car_update.vin))"), nil }). Relation("UpdateManifest"). Select() return updates, errors.WithStack(err) } // SelectByManifestID returns list of car updates by package update id func (c *CarUpdates) SelectByManifestID(manifest_id int64) ([]common.CarUpdate, error) { updates := []common.CarUpdate{} err := c.GetDBConn().Model(&updates).Where("update_manifest.id = ?", manifest_id). Relation("UpdateManifest"). Select() return updates, errors.WithStack(err) } func (c *CarUpdates) SelectOrInsert(update *common.CarUpdate) (bool, error) { return c.insertSelectWithStack(c.GetDBConn().Model(update).Where("vin = ?vin AND update_manifest_id = ?update_manifest_id").SelectOrInsert()) } // Select returns list of cars func (c *CarUpdates) Select(filter *common.CarUpdate, paging *PageQueryOptions) ([]common.CarUpdate, error) { ups := []common.CarUpdate{} query := c.GetDBConn().Model(&ups) c.selectFilter(query, filter) StatusHistoryFilter(query, []string{"cleanup_succeeded"}) c.pageQuery(query, paging) if filter.UpdateManifest != nil && filter.UpdateManifest.ManifestType > 0 { query. Relation("UpdateManifest", func(q *orm.Query) (*orm.Query, error) { return q.Where("manifest_type = ?", filter.UpdateManifest.ManifestType), nil }) } else { query.Relation("UpdateManifest") } err := query.Select() return ups, errors.WithStack(err) } func (c *CarUpdates) Delete(update *common.CarUpdate) (orm.Result, error) { err := validator.ValidateIDField(update.ID) if err != nil { return nil, errors.WithStack(err) } conn := c.GetDBConn() tx, err := conn.Begin() if err != nil { return nil, errors.WithStack(err) } defer func() { if err != nil { tx.Rollback() } tx.Close() }() _, err = tx.Model(&common.CarUpdateStatus{CarUpdateID: update.ID}).Where("car_update_id = ?car_update_id").Delete() if err != nil { return nil, errors.WithStack(err) } result, err := tx.Model(update).WherePK().Delete() if err != nil { return nil, errors.WithStack(err) } err = tx.Commit() return result, errors.WithStack(err) } func (c *CarUpdates) UpdateStatus(update *common.CarUpdate) (orm.Result, error) { result, err := c.UpdateCarUpdate(update) if err != nil { return result, err } _, err = c.LogStatus(update) return result, err } func (c *CarUpdates) UpdateStatusIfNotRepeat(update *common.CarUpdate) (orm.Result, error) { orm, err := c.LogStatusIfNotARepeat(update) if err != nil { return orm, err } result, err := c.UpdateCarUpdate(update) if err != nil { err = errors.WithStack(err) } return result, err } func (c *CarUpdates) UpdateCarUpdate(update *common.CarUpdate) (orm.Result, error) { err := validator.ValidateIDField(update.ID) if err != nil { return nil, errors.WithStack(err) } return c.resultWithStack(c.GetDBConn().Model(update).Column("status", "error_code").WherePK().Update()) } func (c *CarUpdates) LogStatus(update *common.CarUpdate) (orm.Result, error) { // Should this also be updated to have the catch of double statuses status := common.CarUpdateStatus{ CarUpdateID: update.ID, Status: update.Status, ErrorCode: update.ErrorCode, Info: update.Info, } result, err := c.resultWithStack(c.GetDBConn().Model(&status).Insert()) if err != nil { err = errors.WithStack(fmt.Errorf("LogStatus::%s. with status %d %s %d %s", err.Error(), status.CarUpdateID, status.Status, status.ErrorCode, status.Info)) logger.Err(err) } return result, err } // If the last status that was inserted is the same as the one we are trying to insert, we do not do it var RepeatedStatus = errors.New("RepeatedStatus") func (c *CarUpdates) LogStatusIfNotARepeat(update *common.CarUpdate) (orm orm.Result, err error) { logger.Debug().Msgf("attempt to add new status %s for update %d", update.Status, update.ID) // CEC-5650 debugging status := common.CarUpdateStatus{ CarUpdateID: update.ID, Status: update.Status, ErrorCode: update.ErrorCode, Info: update.Info, } // This query insert's our status if the most recent status on the car_update_id does not match the new status // If it does match the new status, then we do not insert // 1: Update ID, 2: Status, 3: error_code, 4: The info, 5: UpdateID, 6: The status query := `INSERT INTO public.car_update_statuses( car_update_id, status, error_code, info) SELECT ?, ?, ?, ? WHERE NOT EXISTS( SELECT 1 FROM (SELECT id, car_update_id, status, error_code, created_at, updated_at, info FROM public.car_update_statuses WHERE car_update_id = ? ORDER BY created_at DESC LIMIT 1) AS temp WHERE status = ?)` orm, err = c.GetDBConn().Exec(query, status.CarUpdateID, status.Status, status.ErrorCode, status.Info, status.CarUpdateID, status.Status) if err != nil { err = errors.WithStack(fmt.Errorf("%s. with status %d %s %d %s", err.Error(), status.CarUpdateID, status.Status, status.ErrorCode, status.Info)) logger.Err(err) return orm, err } if orm.RowsAffected() == 0 { return orm, RepeatedStatus } return } func (c *CarUpdates) Insert(update *common.CarUpdate) (orm.Result, error) { return c.insert(update) } func (c *CarUpdates) InsertAndCreateStatus(update *common.CarUpdate) (res orm.Result, err error) { res, err = c.insert(update) if err != nil { return } return c.LogStatus(update) } func (c *CarUpdates) Load(update *common.CarUpdate) error { query := c.GetDBConn().Model(update) if update.ID > 0 { query.WherePK() } else if update.VIN == "" && update.UpdateManifestID > 0 { query.Where("vin = ?vin AND update_manifest_id = ?update_manifest_id") } else { return errors.New("no id, vin, update_manifest_id") } if update.UpdateManifest != nil && update.UpdateManifest.ManifestType > 0 { query. Relation("UpdateManifest", func(q *orm.Query) (*orm.Query, error) { return q.Where("manifest_type = ?", update.UpdateManifest.ManifestType), nil }) } else { query.Relation("UpdateManifest") } return errors.WithStack(query. Relation("UpdateManifest.ECUs"). Relation("UpdateManifest.ECUs.Files"). Relation("UpdateManifest.ECUs.Files.WriteRegion"). Relation("UpdateManifest.ECUs.Files.EraseRegion"). Select()) } func (c *CarUpdates) Count(filter *common.CarUpdate) (int, error) { query := c.GetDBConn().Model((*common.CarUpdate)(nil)) c.selectFilter(query, filter) return c.countWithStack(query.Count()) } func (c *CarUpdates) CountUpdateStatuses(carupdateid int64) (int, error) { query := c.GetDBConn().Model((*common.CarUpdateStatus)(nil)) return c.countWithStack(query.Where("car_update_id = ?", carupdateid).Count()) } func (c *CarUpdates) GetUpdateStatuses(carupdateid int64, paging *PageQueryOptions) ([]common.CarUpdateStatus, error) { result := []common.CarUpdateStatus{} query := c.GetDBConn().Model(&result) c.pageQuery(query, paging) err := query.Where("car_update_id = ?", carupdateid).Select() if err == nil && len(result) == 0 { return result, errors.WithStack(pg.ErrNoRows) } return result, errors.WithStack(err) } func (c *CarUpdates) TruncateRequirementsAwaitForUpdate(carupdateid int64) (orm.Result, error) { queryString := `with row_nums as (select *, row_number() over (partition by car_update_id order by updated_at) as row from car_update_statuses where status = 'requirements_await' and car_update_id = ?) delete from car_update_statuses where id in ( select id from row_nums where (row != (select max(row) from row_nums)) and (row != 1) )` err := validator.ValidateIDField(carupdateid) if err != nil { return nil, errors.WithStack(err) } conn := c.GetDBConn() tx, err := conn.Begin() if err != nil { return nil, errors.WithStack(err) } defer func() { if err != nil { tx.Rollback() } tx.Close() }() result, err := conn.Query(&common.CarUpdateStatus{}, queryString, carupdateid) if err != nil { return nil, errors.WithStack(err) } err = tx.Commit() return result, errors.WithStack(err) } func (c *CarUpdates) GetManifest(carupdateid int64) (*common.UpdateManifest, error) { manifest := common.UpdateManifest{} err := c.GetDBConn().Model(&manifest).Join("JOIN car_updates").JoinOn("car_updates.update_manifest_id = update_manifest.id").JoinOn("car_updates.id = ?", carupdateid).Relation("ECUs").Relation("ECUs.Files").Select() return &manifest, errors.WithStack(err) } func (c *CarUpdates) HasPendingUpdates(manifestID int64, vin string) (bool, error) { count, err := c.GetDBConn().Model(&common.CarUpdate{}).Where("update_manifest_id = ? AND vin = ? AND status NOT IN (?)", manifestID, vin, pg.In(FINAL_UPDATE_STATUS)).Count() if err != nil { return false, err } return count > 0, nil } // An updateID of 0 means no pending update func (c *CarUpdates) HasPendingUpdatesFromAftersalesUser(manifestID int64, vin string) (updateID int64, pendingUpdateAftersales bool, err error) { var carUpdates []common.CarUpdate err = c.GetDBConn().Model(&carUpdates).Where("update_manifest_id = ? AND vin = ? AND status NOT IN (?)", manifestID, vin, pg.In(FINAL_UPDATE_STATUS)).Select() if err != nil { return } if len(carUpdates) == 0 { return } if len(carUpdates) > 1 { logger.Error().Msgf("Have more than one pending update for manifestID %d on vin %s. This should not happen and our logic needs to change", manifestID, vin) } // This should only ever be one for _, u := range carUpdates { updateID = u.ID if u.UpdateSource == common.UPDATE_SOURCE_AFTERSALES { pendingUpdateAftersales = true break } } return } func (c *CarUpdates) InsertMissingFlashpack(vin string, flashpackVersion string) (err error) { mf := common.MissingFlashpack{ VIN: vin, FlashPackVersion: flashpackVersion, } _, err = c.insert(&mf) return }