package queries import ( "fmt" "fiskerinc.com/modules/common" "fiskerinc.com/modules/db" "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" ) type UpdateManifestsInterface interface { QueryBaseInterface Archive(ids []int64, active bool) (orm.Result, error) Count(filter common.UpdateManifest) (int, error) Delete(manifest *common.UpdateManifest) (orm.Result, error) Insert(manifest *common.UpdateManifest) (orm.Result, error) Select(filter *common.UpdateManifest, paging *PageQueryOptions) ([]common.UpdateManifest, error) SelectByVIN(vin string, paging *PageQueryOptions) ([]common.StatusManifest, error) Update(manifest *common.UpdateManifest) (orm.Result, error) Load(manifest *common.UpdateManifest) error Search(filter common.UpdateManifestSearch, paging *PageQueryOptions) ([]common.UpdateManifest, error) SearchCount(filter common.UpdateManifestSearch) (int, error) ECURollback(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) ECUInsert(manifest *common.UpdateManifestECU) (orm.Result, error) FileInsert(manifest *common.UpdateManifestFile) (orm.Result, error) AddSUMSVersion(manifest *common.UpdateManifest) (orm.Result, error) SelectFlashPackByVersion(versionNumber string) (manifest common.UpdateManifest, err error) } // NewUpdateManifest returns UpdateManifestInterface. // mode allows to use more flexible requests. // If mode is nil, default mode is used. func NewUpdateManifest(mode UpdateManifestMode) UpdateManifestsInterface { if mode == nil { return &UpdateManifests{ mode: DefaultMode{}, } } return &UpdateManifests{ mode: mode, } } type UpdateManifests struct { mode UpdateManifestMode QueryBase } func (um *UpdateManifests) Count(filter common.UpdateManifest) (int, error) { query := um.GetDBConn().Model((*common.UpdateManifest)(nil)) um.selectFilter(query, &filter) return query.Count() } func (um *UpdateManifests) Delete(manifest *common.UpdateManifest) (orm.Result, error) { total := ORMResults{} err := validator.ValidateIDField(manifest.ID) if err != nil { return nil, err } err = um.Load(manifest) if err != nil { return nil, err } tx, err := um.GetDBConn().Begin() if err != nil { return nil, err } _, err = um.manifestDelete(tx, &total, manifest) if err != nil { tx.Rollback() return nil, err } err = tx.Commit() if err != nil { return nil, err } return &total, err } func (um *UpdateManifests) Insert(manifest *common.UpdateManifest) (orm.Result, error) { total := ORMResults{} tx, err := um.GetDBConn().Begin() if err != nil { err = errors.WithStack(err) return nil, err } _, err = um.manifestInsert(tx, &total, manifest) if err != nil { tx.Rollback() err = errors.WithStack(err) return nil, err } err = tx.Commit() if err != nil { err = errors.WithStack(err) return nil, err } return &total, nil } func (um *UpdateManifests) ECUInsert(ecu *common.UpdateManifestECU) (orm.Result, error) { total := ORMResults{} tx, err := um.GetDBConn().Begin() if err != nil { return nil, err } _, err = um.ecuInsert(tx, &total, ecu) if err != nil { tx.Rollback() return nil, err } err = tx.Commit() if err != nil { return nil, err } return &total, nil } func (um *UpdateManifests) FileInsert(file *common.UpdateManifestFile) (orm.Result, error) { total := ORMResults{} tx, err := um.GetDBConn().Begin() if err != nil { return nil, err } _, err = um.fileInsert(tx, &total, file) if err != nil { tx.Rollback() return nil, err } err = tx.Commit() if err != nil { return nil, err } return &total, nil } func (um *UpdateManifests) Select(filter *common.UpdateManifest, paging *PageQueryOptions) ([]common.UpdateManifest, error) { manifests := []common.UpdateManifest{} query := um.GetDBConn().Model(&manifests) um.selectFilter(query, filter) um.pageQuery(query, paging) err := query.Select() return manifests, errors.WithStack(err) } func (um *UpdateManifests) Archive(ids []int64, active bool) (orm.Result, error) { manifests := []common.UpdateManifest{} return um.resultWithStack( um.GetDBConn().Model(&manifests). Set("active = ?", active). Where("id IN (?)", pg.In(ids)). Update(), ) } func (um *UpdateManifests) SelectByVIN(vin string, paging *PageQueryOptions) ([]common.StatusManifest, error) { manifests := []common.StatusManifest{} query := um.GetDBConn().Model(&manifests) query. ColumnExpr("status_manifest.*"). ColumnExpr("car_updates.status as status"). ColumnExpr("car_updates.updated_at as status_updated"). ColumnExpr("status_manifest.created_at as manifest_created"). Join("LEFT JOIN car_updates ON car_updates.update_manifest_id=status_manifest.id"). Where("vin = ?", vin) query = um.mode.SelectByVINCondition(query) query = um.pageQuery(query, paging) err := query.Select() return manifests, errors.WithStack(err) } func (um *UpdateManifests) Update(manifest *common.UpdateManifest) (orm.Result, error) { err := validator.ValidateIDField(manifest.ID) if err != nil { return nil, err } query := um.GetDBConn().Model(manifest).Column( "name", "release_notes", "type", "fingerprint", "rollback_enabled", "update_duration", "max_attempts", ) if manifest.Active != nil { query = query.Column("active") } if manifest.Env != "" { query = query.Column("env") } if manifest.SUMS != "" { query = query.Column("sums") } return query.WherePK().Update() } func (um *UpdateManifests) Load(manifest *common.UpdateManifest) error { query := um.GetDBConn().Model(manifest) if manifest.ID > 0 { query.WherePK() } else if manifest.Name != "" && manifest.Version != "" { query.Where("name = ?name AND version = ?version") } else { return errors.New("no id, name or version") } // For Magna, we will add the manifest type, then we will only get it when it is equal to two if manifest.ManifestType > 0 { query.Where("manifest_type = ?", manifest.ManifestType) } if manifest.Env == "" { manifest.Env = common.EnvCurrent } return um.mode.LoadRelations(query) } func (um *UpdateManifests) Search(filter common.UpdateManifestSearch, paging *PageQueryOptions) ([]common.UpdateManifest, error) { manifests := []common.UpdateManifest{} query := um.GetDBConn().Model(&manifests) um.searchFilter(query, &filter) um.pageQuery(query, paging) err := query.Select() return manifests, errors.WithStack(err) } func (um *UpdateManifests) SearchCount(filter common.UpdateManifestSearch) (int, error) { query := um.GetDBConn().Model((*common.UpdateManifest)(nil)) um.searchFilter(query, &filter) return query.Count() } // Used to get rollbacks for ecu's func (um *UpdateManifests) ECURollback(man *common.UpdateManifestECU, vin string) (rollBackManifest []*common.UpdateManifestECU, err error) { // Use our new rollback system, if it fails return the old get for rollbacks // This may fail as the car_ecus system is a bit corrupted in data targetedRollback, err := um.getECURollbackSpecificToCar(man, vin) if err == nil && targetedRollback != nil { rollBackManifest = append(rollBackManifest, targetedRollback) return } return um.getRollbacksListOfPossible(man) } func (um *UpdateManifests) getECURollbackSpecificToCar(man *common.UpdateManifestECU, vin string) (rollbackManifest *common.UpdateManifestECU, err error) { conn := um.GetDBConn() targetCarECU := common.CarECU{} targetCarECU.VIN = vin targetCarECU.ECU = man.ECU // Using the ecu and vin, get the latest carECU for that specific car err = conn.Model(&targetCarECU). Where("vin = ?", targetCarECU.VIN). Where("ecu = ?", targetCarECU.ECU). Where("version != 'unavailable'"). // We have so many of these though, like its not something that you can even roll back to Order("epoch_usec DESC"). Limit(1). Select() if err != nil { logger.Warn().Err(err).Msgf("failed to get car ecu for rollback for car %s ecu %s", vin, man.ECU) return } if targetCarECU.Version == "" { logger.Warn().Interface("target car ecu", targetCarECU).Msgf("failed to fetch a previous ecu version for car %s ecu %s", vin, man.ECU) err = fmt.Errorf("getRollBackNew error") return } targetManifest := common.UpdateManifestECU{} err = conn.Model(&targetManifest). Where("version = ?", targetCarECU.Version). Where("ecu = ?", targetCarECU.ECU). Select() if err != nil { logger.Warn().Err(err).Msgf("failed to get ecu rollback update manifest ecu for car %s", vin) return } if targetManifest.ID == 0 { logger.Warn().Interface("target ecu", targetCarECU).Msgf("failed to get ecu rollback update manifest ecu for car %s", vin) err = fmt.Errorf("getRollBackNew error") return } return } func (um *UpdateManifests) getRollbacksListOfPossible(man *common.UpdateManifestECU) (rollBackManifest []*common.UpdateManifestECU, err error) { var manifests []*common.UpdateManifestECU conn := um.GetDBConn() sub := conn.Model(&manifests).ColumnExpr("version, max(created_at)"). Where("ecu = ?", man.ECU). Where("hw_versions = ?", pg.Array(man.HWVersions)). Where("version != ?", man.Version). Group("version") err = conn. Model(&manifests). With("sub", sub). Column("update_manifest_ecu.version", "id"). Join("INNER JOIN sub ON update_manifest_ecu.version = sub.version AND "+ "update_manifest_ecu.created_at = sub.max"). Where("ecu = ?", man.ECU). Where("hw_versions = ?", pg.Array(man.HWVersions)). Where("update_manifest_ecu.version != ?", man.Version). Relation("Files"). Relation("Files.WriteRegion"). Relation("Files.EraseRegion"). Order("version"). Select() if err != nil { errors.WithMessagef(err, "failed to do ECURollback() for manifest_id %d on ECU %s id: %d", man.UpdateManifestID, man.ECU, man.ID) return nil, err } return } func (um *UpdateManifests) manifestDelete(operation db.Operation, resultsTotal *ORMResults, manifest *common.UpdateManifest) (orm.Result, error) { // Removed manifest.ecuDelete as the deletes are now propagated by the database result, err := um.resultWithStack(operation.Model(manifest).WherePK().Delete()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } return resultsTotal, nil } func (um *UpdateManifests) manifestInsert(operation db.Operation, resultsTotal *ORMResults, manifest *common.UpdateManifest) (orm.Result, error) { result, err := um.resultWithStack(operation.Model(manifest).Insert()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } resultsTotal.SetModel(result.Model()) if manifest.ECUs != nil { for i := range manifest.ECUs { ecu := manifest.ECUs[i] ecu.UpdateManifestID = manifest.ID result, err = um.ecuInsert(operation, resultsTotal, ecu) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } } } return resultsTotal, nil } func (um *UpdateManifests) ecuDelete(operation db.Operation, resultsTotal *ORMResults, ecu *common.UpdateManifestECU) (orm.Result, error) { if ecu.Files != nil { for i := range ecu.Files { file := ecu.Files[i] result, err := um.fileDelete(operation, resultsTotal, file) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } } } result, err := operation.Model(ecu).WherePK().Delete() if um.hasErrorResult(resultsTotal, result, err) { return nil, err } return resultsTotal, nil } func (um *UpdateManifests) ecuInsert(operation db.Operation, resultsTotal *ORMResults, ecu *common.UpdateManifestECU) (orm.Result, error) { result, err := um.resultWithStack(operation.Model(ecu).Insert()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } if ecu.Files != nil { for i := range ecu.Files { file := ecu.Files[i] file.UpdateManifestECUID = ecu.ID result, err := um.fileInsert(operation, resultsTotal, file) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } } } return resultsTotal, nil } func (um *UpdateManifests) fileDelete(operation db.Operation, resultsTotal *ORMResults, file *common.UpdateManifestFile) (orm.Result, error) { result, err := um.resultWithStack(operation.Model(file).WherePK().Delete()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } resultsTotal.SetModel(result.Model()) result, err = um.resultWithStack(operation.Model(&common.MemoryRegion{ID: file.WriteRegionID}).WherePK().Delete()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } if file.EraseRegionID != 0 { result, err = um.resultWithStack(operation.Model(&common.MemoryRegion{ID: file.EraseRegionID}).WherePK().Delete()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } } result, err = um.fileKeyDelete(operation, resultsTotal, file) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } return resultsTotal, nil } func (um *UpdateManifests) fileKeyDelete(operation db.Operation, resultsTotal *ORMResults, file *common.UpdateManifestFile) (orm.Result, error) { fk := &common.FileKey{FileID: file.FileID} result, err := um.resultWithStack(operation.Model(fk).WherePK().Delete()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } return resultsTotal, err } func (um *UpdateManifests) fileInsert(operation db.Operation, resultsTotal *ORMResults, file *common.UpdateManifestFile) (orm.Result, error) { result, err := um.resultWithStack(operation.Model(&file.WriteRegion).Insert()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } file.WriteRegionID = file.WriteRegion.ID if file.EraseRegion != nil { result, err = um.resultWithStack(operation.Model(file.EraseRegion).Insert()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } file.EraseRegionID = file.EraseRegion.ID } result, err = um.resultWithStack(operation.Model(file).Insert()) if um.hasErrorResult(resultsTotal, result, err) { return nil, err } resultsTotal.SetModel(result.Model()) return resultsTotal, nil } func (um *UpdateManifests) selectFilter(query *orm.Query, filter *common.UpdateManifest) { if filter.ID > 0 { query.Where("id = ?", filter.ID) } if filter.Name != "" { query.Where("name = ?", filter.Name) } if filter.Version != "" { query.Where("version = ?", filter.Version) } if filter.Description != "" { query.Where("description LIKE ?", filter.Description) } if filter.ManifestType > 0 { query.Where("manifest_type = ?", filter.ManifestType) } if filter.Active != nil { query.Where("active = ?", filter.Active) } if filter.Country != "" { query.Where("country = ? or country is null", filter.Country) } if filter.PowerTrain != "" { query.Where("powertrain = ? or powertrain is null", filter.PowerTrain) } if filter.Restraint != "" { query.Where("restraint = ? or restraint is null", filter.Restraint) } if filter.Model != "" { query.Where("model = ? or model is null", filter.Model) } if filter.Trim != "" { query.Where("trim = ? or trim is null", filter.Trim) } if filter.Year != 0 { query.Where("year = ? or year is null", filter.Year) } if filter.BodyType != "" { query.Where("body_type = ? or body_type is null", filter.BodyType) } } func (um *UpdateManifests) searchFilter(query *orm.Query, filter *common.UpdateManifestSearch) { if filter.Search != "" { query.Where("document @@ plainto_tsquery(?)", filter.Search) } if filter.ManifestType == common.SoftwareUpdateType { filter.ManifestType = 0 query.WhereInMulti("manifest_type in (?)", common.SoftwareUpdateType, common.MagnaManifestUpdateType, common.AftersalesUpdateType) } um.selectFilter(query, &filter.UpdateManifest) } func (um *UpdateManifests) AddSUMSVersion(manifest *common.UpdateManifest) (orm.Result, error) { return um.resultWithStack(um.GetDBConn().Model(manifest).Column("sums").Where("id = ?id AND sums IS NULL").Update()) } // Given a flashpack version number: 14.4, we will return the corresponding flashpack func (um *UpdateManifests) SelectFlashPackByVersion(versionNumber string) (manifest common.UpdateManifest, err error) { // Prepare for Like Statement versionNumber = "%" + versionNumber err = um.GetDBConn().Model(&manifest).Where("manifest_type = ? AND name LIKE ?", common.AsBuiltUpdateType, versionNumber).Limit(1).Select() return }