diff --git a/go.mod b/go.mod index 25c56100..aa85daa2 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/volatiletech/null/v8 v8.1.2 github.com/volatiletech/sqlboiler/v4 v4.15.0 github.com/volatiletech/strmangle v0.0.5 + go.uber.org/mock v0.3.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 ) @@ -88,7 +89,6 @@ require ( go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama v0.31.0 // indirect go.opentelemetry.io/otel v1.15.1 // indirect go.opentelemetry.io/otel/trace v1.15.1 // indirect - go.uber.org/mock v0.3.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/mod v0.12.0 // indirect diff --git a/go.sum b/go.sum index b355a5e6..2ed03774 100644 --- a/go.sum +++ b/go.sum @@ -288,7 +288,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/internal/controllers/device_config_controller.go b/internal/controllers/device_config_controller.go index 346ac7b2..fa69d90c 100644 --- a/internal/controllers/device_config_controller.go +++ b/internal/controllers/device_config_controller.go @@ -5,11 +5,14 @@ import ( "database/sql" "encoding/binary" "fmt" + "strings" + + "github.com/volatiletech/sqlboiler/v4/types" - p_grpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" - pb "github.com/DIMO-Network/devices-api/pkg/grpc" "github.com/volatiletech/sqlboiler/v4/queries/qm" + p_grpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" + pb "github.com/DIMO-Network/devices-api/pkg/grpc" "github.com/DIMO-Network/vehicle-signal-decoding/internal/config" "github.com/DIMO-Network/vehicle-signal-decoding/internal/core/services" "github.com/DIMO-Network/vehicle-signal-decoding/internal/infrastructure/db/models" @@ -232,9 +235,65 @@ func (d *DeviceConfigController) GetDBCFileByTemplateName(c *fiber.Ctx) error { } -func (d *DeviceConfigController) getConfigURLs(c *fiber.Ctx, ud *pb.UserDevice) error { - baseURL := d.settings.DeploymentURL +// GetConfigURLsFromVIN godoc +// @Description Retrieve the URLs for PID, DeviceSettings, and DBC configuration based on a given VIN. These could be empty if not configs available +// @Tags vehicle-signal-decoding +// @Produce json +// @Success 200 {object} DeviceConfigResponse "Successfully retrieved configuration URLs" +// @Failure 404 "Not Found - No templates available for the given parameters" +// @Param vin path string true "vehicle identification number (VIN)" +// @Router /device-config/vin/{vin}/urls [get] +func (d *DeviceConfigController) GetConfigURLsFromVIN(c *fiber.Ctx) error { + vin := c.Params("vin") + + ud, err := d.userDeviceSvc.GetUserDeviceByVIN(c.Context(), vin) + if err != nil { + definitionResp, err := d.deviceDefSvc.DecodeVIN(c.Context(), vin) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": fmt.Sprintf("could not decode VIN, contact support if you're sure this is valid VIN: %s", vin)}) + } + // todo: when DecodeVIN supports powertrain, add to below ud + + ud = &pb.UserDevice{ + DeviceDefinitionId: definitionResp.DeviceDefinitionId, + } + if len(definitionResp.DeviceStyleId) > 0 { + ud.DeviceStyleId = &definitionResp.DeviceStyleId + } + } + + return d.getConfigURLs(c, ud) +} +// GetConfigURLsFromEthAddr godoc +// @Description Retrieve the URLs for PID, DeviceSettings, and DBC configuration based on device's Ethereum Address. These could be empty if not configs available +// @Tags vehicle-signal-decoding +// @Produce json +// @Success 200 {object} DeviceConfigResponse "Successfully retrieved configuration URLs" +// @Failure 404 "Not Found - No templates available for the given parameters" +// @Failure 400 "incorrect eth addr format" +// @Param ethAddr path string false "Ethereum Address" +// @Router /device-config/eth-addr/{ethAddr}/urls [get] +func (d *DeviceConfigController) GetConfigURLsFromEthAddr(c *fiber.Ctx) error { + ethAddr := c.Params("ethAddr") + ud, err := d.userDeviceSvc.GetUserDeviceByEthAddr(c.Context(), ethAddr) + if err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": fmt.Sprintf("no connected user device found for EthAddr: %s", ethAddr)}) + } + return d.getConfigURLs(c, ud) +} + +func padByteArray(input []byte, targetLength int) []byte { + if len(input) >= targetLength { + return input // No need to pad if the input is already longer or equal to the target length + } + + padded := make([]byte, targetLength-len(input)) + return append(padded, input...) +} + +// setCANProtocol converts autopi style Protocol to our VSD style protocol, but always returning a default if nothing found +func (d *DeviceConfigController) setCANProtocol(ud *pb.UserDevice) { switch ud.CANProtocol { case "6": ud.CANProtocol = models.CanProtocolTypeCAN11_500 @@ -243,16 +302,29 @@ func (d *DeviceConfigController) getConfigURLs(c *fiber.Ctx, ud *pb.UserDevice) case "": ud.CANProtocol = models.CanProtocolTypeCAN11_500 } +} + +// retrieveAndSetVehicleInfo figures out what if any device definition information corresponds to the UserDevice. +// also calls setPowerTrainType to find and set a default Powertrain +func (d *DeviceConfigController) retrieveAndSetVehicleInfo(ctx context.Context, ud *pb.UserDevice) (string, string, int, error) { - // Device Definitions var ddResponse *p_grpc.GetDeviceDefinitionItemResponse deviceDefinitionID := ud.DeviceDefinitionId - ddResponse, err := d.deviceDefSvc.GetDeviceDefinitionByID(c.Context(), deviceDefinitionID) + ddResponse, err := d.deviceDefSvc.GetDeviceDefinitionByID(ctx, deviceDefinitionID) if err != nil { - return err + return "", "", 0, fmt.Errorf("failed to retrieve device definition for deviceDefinitionId %s: %w", deviceDefinitionID, err) } + vehicleYear := int(ddResponse.Type.Year) + vehicleMake := ddResponse.Type.MakeSlug + vehicleModel := ddResponse.Type.ModelSlug + + d.setPowerTrainType(ddResponse, ud) + + return vehicleMake, vehicleModel, vehicleYear, nil +} +func (d *DeviceConfigController) setPowerTrainType(ddResponse *p_grpc.GetDeviceDefinitionItemResponse, ud *pb.UserDevice) { var powerTrainType string for _, attribute := range ddResponse.DeviceAttributes { if attribute.Name == "powertrain_type" { @@ -266,124 +338,172 @@ func (d *DeviceConfigController) getConfigURLs(c *fiber.Ctx, ud *pb.UserDevice) ud.PowerTrainType = "ICE" } } +} - // Query templates, filter by protocol and powertrain - templates, err := models.Templates( - models.TemplateWhere.Protocol.EQ(ud.CANProtocol), - models.TemplateWhere.Powertrain.EQ(ud.PowerTrainType), - qm.Load(models.TemplateRels.TemplateNameDBCFile), - qm.Load(models.TemplateRels.TemplateNameTemplateVehicles), - qm.Load(models.TemplateRels.TemplateNameDeviceSetting), - ).All(context.Background(), d.db) - - if err != nil { - // todo what if err is sql.ErrNoRows - eg. nothing found? we would probably want to return the first default template - // todo - this should just return the wrapped error and let the api.ErrorHandler deal with how to return the error - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": fmt.Sprintf("Failed to query templates for protocol: %s and powertrain: %s", ud.CANProtocol, ud.PowerTrainType)}) - } - - // Filter templates by vehicle year range - var matchedTemplate *models.Template - for _, template := range templates { - for _, tv := range template.R.TemplateNameTemplateVehicles { - if vehicleYear >= tv.YearStart && vehicleYear <= tv.YearEnd { - matchedTemplate = template - break - } - } - if matchedTemplate != nil { - break - } +// selectAndFetchTemplate figures out the right template to use based on the protocol, powertrain, year range, make, and /or model. +// Returns default template if nothing found. Requirees ud.CANProtocol and Powertrain to be set to something +func (d *DeviceConfigController) selectAndFetchTemplate(ctx context.Context, ud *pb.UserDevice, vehicleMake, vehicleModel string, vehicleYear int) (*models.Template, error) { + // guard + if ud.CANProtocol == "" { + return nil, fmt.Errorf("CANProtocol is required in the user device") } - if matchedTemplate == nil { - // todo - what if templates length is 0? maybe handle this further above - matchedTemplate = templates[0] + if ud.PowerTrainType == "" { + return nil, fmt.Errorf("PowerTrainType is required in the user device") } - templateName := matchedTemplate.TemplateName - var parentTemplateName string - if matchedTemplate.ParentTemplateName.Valid { - parentTemplateName = matchedTemplate.ParentTemplateName.String - } else { - parentTemplateName = templateName + var matchedTemplateName string + + // First, try to find a template based on device definitions + deviceDefinitions, err := models.TemplateDeviceDefinitions( + models.TemplateDeviceDefinitionWhere.DeviceDefinitionID.EQ(ud.DeviceDefinitionId), + ).All(ctx, d.db) + + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("failed to query template device definitions: %w", err) } - version := matchedTemplate.Version - response := DeviceConfigResponse{ - PidURL: fmt.Sprintf("%s/v1/device-config/%s/pids", baseURL, templateName), - Version: version, + if len(deviceDefinitions) > 0 { + matchedTemplateName = deviceDefinitions[0].TemplateName } - if len(templates) > 0 { - if templates[0].R != nil { - // only set dbc url if we have dbc - if templates[0].R.TemplateNameDBCFile != nil && len(templates[0].R.TemplateNameDBCFile.DBCFile) > 0 { - response.DbcURL = fmt.Sprintf("%s/v1/device-config/%s/dbc", baseURL, templateName) - } - // only set device settings url if we have one - if templates[0].R.TemplateNameDeviceSetting != nil { - response.DeviceSettingURL = fmt.Sprintf("%s/v1/device-config/%s/device-settings", baseURL, parentTemplateName) + // Second, try to find a template based on Year, then Make & Model + if matchedTemplateName == "" { + // compare by year first, then in memory below we'll look for make and/or model + templateVehicles, err := models.TemplateVehicles( + models.TemplateVehicleWhere.YearStart.LTE(vehicleYear), + models.TemplateVehicleWhere.YearEnd.GTE(vehicleYear), + qm.Load(models.TemplateVehicleRels.TemplateNameTemplate), + ).All(ctx, d.db) + + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("failed to query templates for make: %s, model: %s, year: %d: %w", vehicleMake, vehicleModel, vehicleYear, err) + } + // if anything is returned, try finding a match by make and/or model + if len(templateVehicles) > 0 { + for _, tv := range templateVehicles { + // any matches for year & same protocol + if tv.R.TemplateNameTemplate.Protocol == ud.CANProtocol { + matchedTemplateName = tv.TemplateName + // now any matches for make + if tv.MakeSlug == vehicleMake { + matchedTemplateName = tv.TemplateName + // now see if there is also a model match + if modelMatch(tv.ModelWhitelist, vehicleModel) { + break + } + } + } } } } - return c.JSON(response) -} + // Third, fallback to query by protocol and powertrain. Match by protocol first + if matchedTemplateName == "" { + templates, err := models.Templates( + models.TemplateWhere.Protocol.EQ(ud.CANProtocol), + ).All(ctx, d.db) -// GetConfigURLsFromVIN godoc -// @Description Retrieve the URLs for PID, DeviceSettings, and DBC configuration based on a given VIN. These could be empty if not configs available -// @Tags vehicle-signal-decoding -// @Produce json -// @Success 200 {object} DeviceConfigResponse "Successfully retrieved configuration URLs" -// @Failure 404 "Not Found - No templates available for the given parameters" -// @Param vin path string true "vehicle identification number (VIN)" -// @Router /device-config/vin/{vin}/urls [get] -func (d *DeviceConfigController) GetConfigURLsFromVIN(c *fiber.Ctx) error { - vin := c.Params("vin") + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("failed to query templates for protocol: %s and powertrain: %s: %w", ud.CANProtocol, ud.PowerTrainType, err) + } + if len(templates) > 0 { + // match the first one just in case + matchedTemplateName = templates[0].TemplateName + // now see if also have a powertrain match + for _, t := range templates { + if t.Powertrain == ud.PowerTrainType { + matchedTemplateName = t.TemplateName + break + } + } + } + } + + // Fallback to default template if still no match is found + if matchedTemplateName == "" { + defaultTemplates, err := models.Templates( + models.TemplateWhere.TemplateName.LIKE("default%"), + ).All(ctx, d.db) - ud, err := d.userDeviceSvc.GetUserDeviceByVIN(c.Context(), vin) - // if there is no user device with this VIN, then just decode the vin and return the corresponding definition - if err != nil { - definitionResp, err := d.deviceDefSvc.DecodeVIN(c.Context(), vin) if err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": fmt.Sprintf("could not decode VIN, contact support if you're sure this is valid VIN: %s", vin)}) + return nil, fmt.Errorf("failed to query for default templates: %w", err) } - ud = &pb.UserDevice{ - DeviceDefinitionId: definitionResp.DeviceDefinitionId, + if len(defaultTemplates) > 0 { + matchedTemplateName = defaultTemplates[0].TemplateName + } else { + return nil, errors.New("no default templates found") } - if len(definitionResp.DeviceStyleId) > 0 { - ud.DeviceStyleId = &definitionResp.DeviceStyleId - } - // todo: get powertrain type from definition response and include in ud.PowerTrainType } - return d.getConfigURLs(c, ud) + // Fetch the template object if a name was found + matchedTemplate, err := models.Templates( + models.TemplateWhere.TemplateName.EQ(matchedTemplateName), + qm.Load(models.TemplateRels.TemplateNameDBCFile), + qm.Load(models.TemplateRels.TemplateNameDeviceSetting), + ).One(ctx, d.db) + if err != nil { + return nil, fmt.Errorf("failed to fetch template by name %s: %w", matchedTemplateName, err) + } + + return matchedTemplate, nil } -// GetConfigURLsFromEthAddr godoc -// @Description Retrieve the URLs for PID, DeviceSettings, and DBC configuration based on device's Ethereum Address. These could be empty if not configs available -// @Tags vehicle-signal-decoding -// @Produce json -// @Success 200 {object} DeviceConfigResponse "Successfully retrieved configuration URLs" -// @Failure 404 "Not Found - No templates available for the given parameters" -// @Failure 400 "incorrect eth addr format" -// @Param ethAddr path string false "Ethereum Address" -// @Router /device-config/eth-addr/{ethAddr}/urls [get] -func (d *DeviceConfigController) GetConfigURLsFromEthAddr(c *fiber.Ctx) error { - ethAddr := c.Params("ethAddr") - ud, err := d.userDeviceSvc.GetUserDeviceByEthAddr(c.Context(), ethAddr) - if err != nil { - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": fmt.Sprintf("no connected user device found for EthAddr: %s", ethAddr)}) +// modelMatch simply returns if the modelName is in the model List +func modelMatch(modelList types.StringArray, modelName string) bool { + for _, m := range modelList { + if strings.EqualFold(m, modelName) { + return true + } } - return d.getConfigURLs(c, ud) + return false } -func padByteArray(input []byte, targetLength int) []byte { - if len(input) >= targetLength { - return input // No need to pad if the input is already longer or equal to the target length +func (d *DeviceConfigController) getConfigURLs(c *fiber.Ctx, ud *pb.UserDevice) error { + d.setCANProtocol(ud) + + vehicleMake, vehicleModel, vehicleYear, err := d.retrieveAndSetVehicleInfo(c.Context(), ud) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Failed to retrieve device definition: %s", ud.DeviceDefinitionId)) } - padded := make([]byte, targetLength-len(input)) - return append(padded, input...) + matchedTemplate, err := d.selectAndFetchTemplate(c.Context(), ud, vehicleMake, vehicleModel, vehicleYear) + if err != nil { + return err + } + if matchedTemplate == nil { + return errors.New("matched template is nil") + } + + baseURL := d.settings.DeploymentURL + templateName := matchedTemplate.TemplateName + + var parentTemplateName string + if matchedTemplate.ParentTemplateName.Valid { + parentTemplateName = matchedTemplate.ParentTemplateName.String + } else { + parentTemplateName = templateName + } + version := matchedTemplate.Version + + response := DeviceConfigResponse{ + PidURL: fmt.Sprintf("%s/v1/device-config/%s/pids", baseURL, templateName), + Version: version, + } + + // only set dbc url if we have dbc + if matchedTemplate.R.TemplateNameDBCFile != nil && len(matchedTemplate.R.TemplateNameDBCFile.DBCFile) > 0 { + response.DbcURL = fmt.Sprintf("%s/v1/device-config/%s/dbc", baseURL, templateName) + } else { + response.DbcURL = "" + } + + // only set device settings url if we have one + if matchedTemplate.R.TemplateNameDeviceSetting != nil { + response.DeviceSettingURL = fmt.Sprintf("%s/v1/device-config/%s/device-settings", baseURL, parentTemplateName) + } else { + response.DeviceSettingURL = "" + } + + return c.JSON(response) } diff --git a/internal/controllers/device_config_controller_test.go b/internal/controllers/device_config_controller_test.go index 683b1ce0..adf93c37 100644 --- a/internal/controllers/device_config_controller_test.go +++ b/internal/controllers/device_config_controller_test.go @@ -9,6 +9,10 @@ import ( "os" "testing" + _ "github.com/lib/pq" + + "github.com/volatiletech/sqlboiler/v4/types" + p_grpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" pb "github.com/DIMO-Network/devices-api/pkg/grpc" @@ -26,8 +30,6 @@ import ( "github.com/gofiber/fiber/v2" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" - - _ "github.com/lib/pq" //nolint ) const migrationsDirRelPath = "../infrastructure/db/migrations" @@ -475,13 +477,11 @@ func TestGetConfigURLsEmptyDeviceSettings(t *testing.T) { assert.Equal(t, template.Version, receivedResp.Version) - // Teardown: cleanup after test test.TruncateTables(pdb.DBS().Writer.DB, t) }) } - -func TestGetConfigURLsMatchingYearRange(t *testing.T) { +func TestGetConfigURLsDecodeVIN(t *testing.T) { // Arrange mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -493,7 +493,6 @@ func TestGetConfigURLsMatchingYearRange(t *testing.T) { ctx := context.Background() - // Spin up test database in a Docker container pdb, container := test.StartContainerDatabase(ctx, t, migrationsDirRelPath) defer func() { if err := container.Terminate(ctx); err != nil { @@ -502,100 +501,246 @@ func TestGetConfigURLsMatchingYearRange(t *testing.T) { }() vin := "TMBEK6NW1N3088739" - mockedUserDevice := &pb.UserDevice{ - Id: ksuid.New().String(), - UserId: ksuid.New().String(), - Vin: &vin, - DeviceDefinitionId: ksuid.New().String(), - VinConfirmed: true, - CountryCode: "USA", - PowerTrainType: "HEV", - CANProtocol: "7", - PostalCode: "48025", - GeoDecodedCountry: "USA", - GeoDecodedStateProv: "MI", + // Insert template "some-template" into the database + template := &models.Template{ + TemplateName: "some-template", + Version: "1.0", + Protocol: models.CanProtocolTypeCAN11_500, + Powertrain: "HEV", } + err := template.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) + + // Insert device settings for "some-template" + ds := &models.DeviceSetting{ + TemplateName: template.TemplateName, + } + err = ds.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) - mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) - mockUserDeviceSvc.EXPECT().GetUserDeviceByVIN(gomock.Any(), vin).Return(mockedUserDevice, nil) // Mock the device definition service mockedDeviceDefinition := &p_grpc.GetDeviceDefinitionItemResponse{ DeviceDefinitionId: ksuid.New().String(), Type: &p_grpc.DeviceType{ - Year: 2020, + Year: 2020, + MakeSlug: "Ford", + ModelSlug: "Mustang", }, + DeviceAttributes: []*p_grpc.DeviceTypeAttribute{{ + Name: "powertrain_type", + Value: "HEV", + }}, } + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) + mockUserDeviceSvc.EXPECT().GetUserDeviceByVIN(gomock.Any(), vin).Return(nil, errors.New("user device not found")) + mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) - mockDeviceDefSvc.EXPECT().GetDeviceDefinitionByID(gomock.Any(), gomock.Any()).Return(mockedDeviceDefinition, nil) - c := NewDeviceConfigController(&config.Settings{Port: "3000", DeploymentURL: "http://localhost:3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) + mockDeviceDefSvc.EXPECT().DecodeVIN(gomock.Any(), vin).Return(&p_grpc.DecodeVinResponse{ + DeviceDefinitionId: mockedDeviceDefinition.DeviceDefinitionId, + }, nil) + mockDeviceDefSvc.EXPECT().GetDeviceDefinitionByID(gomock.Any(), mockedDeviceDefinition.DeviceDefinitionId).Return(mockedDeviceDefinition, nil) - // insert template in DB - template := &models.Template{ - TemplateName: "some-template", - Version: "1.0", - Protocol: models.CanProtocolTypeCAN29_500, - Powertrain: "HEV", - } - err := template.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) - require.NoError(t, err) + c := NewDeviceConfigController(&config.Settings{Port: "3000", DeploymentURL: "http://localhost:3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) app := fiber.New() app.Get("/config-urls/:vin", c.GetConfigURLsFromVIN) - t.Run("GET - Config URLs by VIN with Matching Year Range", func(t *testing.T) { - // Insert DBCFile in the database - dbcFile := &models.DBCFile{ - TemplateName: "some-template", - DBCFile: "sample-dbc-file-name", - } - err := dbcFile.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) - require.NoError(t, err) - // Add vehicle year range to the template - templateVehicle := &models.TemplateVehicle{ - TemplateName: template.TemplateName, - YearStart: 2019, - YearEnd: 2100, - } - err2 := templateVehicle.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) - require.NoError(t, err2) - ds := models.DeviceSetting{ - TemplateName: "some-template", - BatteryCriticalLevelVoltage: 3.2, - SafetyCutOutVoltage: 2.8, - SleepTimerEventDrivenInterval: 5, + // Act + request := test.BuildRequest("GET", "/config-urls/"+vin, "") + response, err := app.Test(request) + require.NoError(t, err) + + body, _ := io.ReadAll(response.Body) + + // Assert + assert.Equal(t, fiber.StatusOK, response.StatusCode, "response body: "+string(body)) + + var receivedResp DeviceConfigResponse + err = json.Unmarshal(body, &receivedResp) + assert.NoError(t, err) + + //"some-template" + assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/pids", template.TemplateName), receivedResp.PidURL) + assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/device-settings", template.TemplateName), receivedResp.DeviceSettingURL) + assert.Equal(t, "", receivedResp.DbcURL) + assert.Equal(t, template.Version, receivedResp.Version) + + test.TruncateTables(pdb.DBS().Writer.DB, t) +} + +func TestRetrieveAndSetVehicleInfo(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) + + logger := zerolog.New(os.Stdout) + + pdb, container := test.StartContainerDatabase(context.Background(), t, migrationsDirRelPath) + defer func() { + if err := container.Terminate(context.Background()); err != nil { + t.Fatal(err) } - err3 := ds.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) - require.NoError(t, err3) + }() - // Act: make the request - request := test.BuildRequest("GET", "/config-urls/"+vin, "") - response, err := app.Test(request) - require.NoError(t, err) - body, _ := io.ReadAll(response.Body) + c := NewDeviceConfigController( + &config.Settings{Port: "3000"}, + &logger, + pdb.DBS().Reader.DB, + mockUserDeviceSvc, + mockDeviceDefSvc, + ) - // Assert: check the results - if !assert.Equal(t, fiber.StatusOK, response.StatusCode) { - fmt.Println("response body: " + string(body)) + ud := &pb.UserDevice{ + DeviceDefinitionId: "some-definition-id", + } + + expectedDDResponse := &p_grpc.GetDeviceDefinitionItemResponse{ + Type: &p_grpc.DeviceType{ + Year: 2021, + MakeSlug: "Ford", + ModelSlug: "Mustang", + }, + } + + mockDeviceDefSvc.EXPECT(). + GetDeviceDefinitionByID(gomock.Any(), ud.DeviceDefinitionId). + Return(expectedDDResponse, nil) + + vehicleMake, vehicleModel, vehicleYear, err := c.retrieveAndSetVehicleInfo(context.Background(), ud) + + // Assert: + require.NoError(t, err) + assert.Equal(t, "Ford", vehicleMake) + assert.Equal(t, "Mustang", vehicleModel) + assert.Equal(t, 2021, vehicleYear) + +} +func TestSetPowerTrainType(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) + + logger := zerolog.New(os.Stdout) + + pdb, container := test.StartContainerDatabase(context.Background(), t, migrationsDirRelPath) + defer func() { + if err := container.Terminate(context.Background()); err != nil { + t.Fatal(err) } + }() - var receivedResp DeviceConfigResponse - err = json.Unmarshal(body, &receivedResp) - assert.NoError(t, err) + c := NewDeviceConfigController( + &config.Settings{Port: "3000"}, + &logger, + pdb.DBS().Reader.DB, + mockUserDeviceSvc, + mockDeviceDefSvc, + ) + + // Define test cases + testCases := []struct { + name string + deviceAttrs []*p_grpc.DeviceTypeAttribute + expectedPower string + }{ + { + name: "With Specified Powertrain", + deviceAttrs: []*p_grpc.DeviceTypeAttribute{ + {Name: "powertrain_type", Value: "Electric"}, + }, + expectedPower: "Electric", + }, + { + name: "Without Specified Powertrain", + deviceAttrs: []*p_grpc.DeviceTypeAttribute{}, + expectedPower: "ICE", // Default value + }, + } - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/pids", template.TemplateName), receivedResp.PidURL) - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/device-settings", template.TemplateName), receivedResp.DeviceSettingURL) - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/dbc", template.TemplateName), receivedResp.DbcURL) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Create a mock GetDeviceDefinitionItemResponse + ddResponse := &p_grpc.GetDeviceDefinitionItemResponse{ + DeviceAttributes: tc.deviceAttrs, + } - assert.Equal(t, template.Version, receivedResp.Version) + ud := &pb.UserDevice{} - // Teardown: cleanup after test - test.TruncateTables(pdb.DBS().Writer.DB, t) - }) + // Act: + c.setPowerTrainType(ddResponse, ud) + // Assert: + assert.Equal(t, tc.expectedPower, ud.PowerTrainType) + }) + } } -func TestGetConfigURLsNonMatchingYearRange(t *testing.T) { +func TestSetCANProtocol(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) + + logger := zerolog.New(os.Stdout) + + pdb, container := test.StartContainerDatabase(context.Background(), t, migrationsDirRelPath) + defer func() { + if err := container.Terminate(context.Background()); err != nil { + t.Fatal(err) + } + }() + + c := NewDeviceConfigController( + &config.Settings{Port: "3000"}, + &logger, + pdb.DBS().Reader.DB, + mockUserDeviceSvc, + mockDeviceDefSvc, + ) + + // Define test cases + testCases := []struct { + name string + initialCAN string + expectedCAN string + }{ + { + name: "CAN Protocol 6", + initialCAN: "6", + expectedCAN: models.CanProtocolTypeCAN11_500, + }, + { + name: "CAN Protocol 7", + initialCAN: "7", + expectedCAN: models.CanProtocolTypeCAN29_500, + }, + { + name: "Empty CAN Protocol", + initialCAN: "", + expectedCAN: models.CanProtocolTypeCAN11_500, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ud := &pb.UserDevice{CANProtocol: tc.initialCAN} + + // Act + c.setCANProtocol(ud) + + // Assert + assert.Equal(t, tc.expectedCAN, ud.CANProtocol) + }) + } +} + +func TestSelectAndFetchTemplate_DeviceDefinitions(t *testing.T) { // Arrange mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -607,108 +752,136 @@ func TestGetConfigURLsNonMatchingYearRange(t *testing.T) { ctx := context.Background() - // Spin up test database in a Docker container pdb, container := test.StartContainerDatabase(ctx, t, migrationsDirRelPath) defer func() { if err := container.Terminate(ctx); err != nil { t.Fatal(err) } }() - vin := "TMBEK6NW1N3088739" - - mockedUserDevice := &pb.UserDevice{ - Id: ksuid.New().String(), - UserId: ksuid.New().String(), - Vin: &vin, - DeviceDefinitionId: ksuid.New().String(), - VinConfirmed: true, - CountryCode: "USA", - PowerTrainType: "HEV", - CANProtocol: "7", - PostalCode: "48025", - GeoDecodedCountry: "USA", - GeoDecodedStateProv: "MI", - } mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) - mockUserDeviceSvc.EXPECT().GetUserDeviceByVIN(gomock.Any(), vin).Return(mockedUserDevice, nil) - // Mock the device definition service - mockedDeviceDefinition := &p_grpc.GetDeviceDefinitionItemResponse{ - DeviceDefinitionId: ksuid.New().String(), - Type: &p_grpc.DeviceType{ - Year: 2019, - }, - } - mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) - mockDeviceDefSvc.EXPECT().GetDeviceDefinitionByID(gomock.Any(), gomock.Any()).Return(mockedDeviceDefinition, nil) - c := NewDeviceConfigController(&config.Settings{Port: "3000", DeploymentURL: "http://localhost:3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) + c := NewDeviceConfigController(&config.Settings{Port: "3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) - // insert template in DB + // Insert template into the database template := &models.Template{ TemplateName: "some-template", Version: "1.0", Protocol: models.CanProtocolTypeCAN29_500, Powertrain: "HEV", } - err := template.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) + err := template.Insert(ctx, pdb.DBS().Writer, boil.Infer()) require.NoError(t, err) - app := fiber.New() - app.Get("/config-urls/:vin", c.GetConfigURLsFromVIN) + // Insert a matching template device definition + deviceDef := &models.TemplateDeviceDefinition{ + DeviceDefinitionID: "device-def-id", + TemplateName: template.TemplateName, + } + err = deviceDef.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) - t.Run("GET - Config URLs by VIN with Non-Matching Year Range", func(t *testing.T) { - // Insert DBCFile in the database - dbcFile := &models.DBCFile{ - TemplateName: "some-template", - DBCFile: "sample-dbc-file-name", - } - err := dbcFile.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) - require.NoError(t, err) - // Add vehicle year range to the template - templateVehicle := &models.TemplateVehicle{ - TemplateName: template.TemplateName, - YearStart: 2010, - YearEnd: 2015, - } - err2 := templateVehicle.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) - require.NoError(t, err2) + // Create a mocked user device + mockedUserDevice := &pb.UserDevice{ + Id: ksuid.New().String(), + UserId: ksuid.New().String(), + DeviceDefinitionId: ksuid.New().String(), + CANProtocol: models.CanProtocolTypeCAN29_500, + PowerTrainType: "HEV", + } + + vehicleMake := "Ford" + vehicleModel := "Mustang" + vehicleYear := 2021 - ds := models.DeviceSetting{ - TemplateName: "some-template", - BatteryCriticalLevelVoltage: 3.2, - SafetyCutOutVoltage: 2.8, - SleepTimerEventDrivenInterval: 5, + // Act + fetchedTemplate, err := c.selectAndFetchTemplate(ctx, mockedUserDevice, vehicleMake, vehicleModel, vehicleYear) + + // Assert + require.NoError(t, err) + assert.NotNil(t, fetchedTemplate) + assert.Equal(t, template.TemplateName, fetchedTemplate.TemplateName) + + test.TruncateTables(pdb.DBS().Writer.DB, t) +} + +func TestSelectAndFetchTemplate_MMY(t *testing.T) { + // Arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + logger := zerolog.New(os.Stdout).With(). + Timestamp(). + Str("app", "vehicle-signal-decoding"). + Logger() + + ctx := context.Background() + + pdb, container := test.StartContainerDatabase(ctx, t, migrationsDirRelPath) + defer func() { + if err := container.Terminate(ctx); err != nil { + t.Fatal(err) } - err3 := ds.Insert(context.Background(), pdb.DBS().Writer, boil.Infer()) - require.NoError(t, err3) + }() - // Act: make the request - request := test.BuildRequest("GET", "/config-urls/"+vin, "") - response, err := app.Test(request) - require.NoError(t, err) - body, _ := io.ReadAll(response.Body) + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) + mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) + c := NewDeviceConfigController(&config.Settings{Port: "3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) - // Assert: check the results - assert.Equal(t, fiber.StatusOK, response.StatusCode, "response body: "+string(body)) + decoy := &models.Template{ + TemplateName: "mmy-template-decoy", + Version: "1.0", + Protocol: models.CanProtocolTypeCAN29_500, + Powertrain: "HEV", + } + err := decoy.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) + // Insert template into the database + template := &models.Template{ + TemplateName: "mmy-template", + Version: "1.0", + Protocol: models.CanProtocolTypeCAN29_500, + Powertrain: "HEV", + } + err = template.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) - var receivedResp DeviceConfigResponse - err = json.Unmarshal(body, &receivedResp) - assert.NoError(t, err) + // Insert a template vehicle that matches the MMY + templateVehicle := &models.TemplateVehicle{ + TemplateName: template.TemplateName, + MakeSlug: "Ford", + ModelWhitelist: types.StringArray{"Mustang"}, + YearStart: 2010, + YearEnd: 2025, + } + err = templateVehicle.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/pids", template.TemplateName), receivedResp.PidURL) - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/device-settings", template.TemplateName), receivedResp.DeviceSettingURL) - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/dbc", template.TemplateName), receivedResp.DbcURL) + // Create a mocked user device without a matching device definition + mockedUserDevice := &pb.UserDevice{ + Id: ksuid.New().String(), + UserId: ksuid.New().String(), + DeviceDefinitionId: "non-existing-def-id", + CANProtocol: models.CanProtocolTypeCAN29_500, + PowerTrainType: "HEV", + } - assert.Equal(t, template.Version, receivedResp.Version) + vehicleMake := "Ford" + vehicleModel := "Mustang" + vehicleYear := 2021 - // Teardown: cleanup after test - test.TruncateTables(pdb.DBS().Writer.DB, t) - }) + // Act + fetchedTemplate, err := c.selectAndFetchTemplate(ctx, mockedUserDevice, vehicleMake, vehicleModel, vehicleYear) + + // Assert + require.NoError(t, err) + assert.NotNil(t, fetchedTemplate) + assert.Equal(t, template.TemplateName, fetchedTemplate.TemplateName) + test.TruncateTables(pdb.DBS().Writer.DB, t) } -func TestGetConfigURLsDecodeVin(t *testing.T) { +func TestSelectAndFetchTemplate_YearRange(t *testing.T) { // Arrange mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -720,82 +893,224 @@ func TestGetConfigURLsDecodeVin(t *testing.T) { ctx := context.Background() - // Spin up test database in a Docker container pdb, container := test.StartContainerDatabase(ctx, t, migrationsDirRelPath) defer func() { if err := container.Terminate(ctx); err != nil { t.Fatal(err) } }() - vin := "TMBEK6NW1N3088739" + + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) + mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) + c := NewDeviceConfigController(&config.Settings{Port: "3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) + // insert another template to have more test data - we should not get this one + template2 := &models.Template{ + TemplateName: "default-template", + Version: "1.0", + Protocol: models.CanProtocolTypeCAN11_500, + Powertrain: "ICE", + } + err := template2.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) + + // Insert template we expect to get template := &models.Template{ - TemplateName: "some-template", + TemplateName: "2019plus-template", Version: "1.0", Protocol: models.CanProtocolTypeCAN11_500, - Powertrain: "HEV", + Powertrain: "ICE", } - err := template.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + err = template.Insert(ctx, pdb.DBS().Writer, boil.Infer()) require.NoError(t, err) - // insert device settings - ds := &models.DeviceSetting{ - TemplateName: "some-template", + + // Insert a template vehicle that matches the year range + templateVehicle := &models.TemplateVehicle{ + TemplateName: template.TemplateName, + YearStart: 2019, + YearEnd: 2025, } - err = ds.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + err = templateVehicle.Insert(ctx, pdb.DBS().Writer, boil.Infer()) require.NoError(t, err) - dd := &p_grpc.GetDeviceDefinitionItemResponse{ - DeviceDefinitionId: ksuid.New().String(), - Type: &p_grpc.DeviceType{ - Year: 2020, - }, - Make: &p_grpc.DeviceMake{ - Id: ksuid.New().String(), - }, - DeviceAttributes: []*p_grpc.DeviceTypeAttribute{{ - Name: "powertrain_type", - Value: template.Powertrain, - }, - }, + // Create a mocked user device without a matching device definition + mockedUserDevice := &pb.UserDevice{ + Id: ksuid.New().String(), + UserId: ksuid.New().String(), + DeviceDefinitionId: "some-2019-vehicle", + PowerTrainType: template.Powertrain, + CANProtocol: template.Protocol, } + vehicleMake := "Ford" + vehicleModel := "Mustang" + vehicleYear := 2019 + + // Act + fetchedTemplate, err := c.selectAndFetchTemplate(ctx, mockedUserDevice, vehicleMake, vehicleModel, vehicleYear) + + // Assert + require.NoError(t, err) + assert.NotNil(t, fetchedTemplate) + assert.Equal(t, template.TemplateName, fetchedTemplate.TemplateName) + + test.TruncateTables(pdb.DBS().Writer.DB, t) +} + +func TestSelectAndFetchTemplate_PowertrainProtocol(t *testing.T) { + // Arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + logger := zerolog.New(os.Stdout).With(). + Timestamp(). + Str("app", "vehicle-signal-decoding"). + Logger() + + ctx := context.Background() + + pdb, container := test.StartContainerDatabase(ctx, t, migrationsDirRelPath) + defer func() { + if err := container.Terminate(ctx); err != nil { + t.Fatal(err) + } + }() + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) - mockUserDeviceSvc.EXPECT().GetUserDeviceByVIN(gomock.Any(), vin).Return(nil, errors.New("user device not found")) mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) - mockDeviceDefSvc.EXPECT().DecodeVIN(gomock.Any(), vin).Return(&p_grpc.DecodeVinResponse{ - DeviceMakeId: dd.Make.Id, - DeviceDefinitionId: dd.DeviceDefinitionId, - DeviceStyleId: "", - Year: 2022, - Source: "", - }, nil) + c := NewDeviceConfigController(&config.Settings{Port: "3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) - mockDeviceDefSvc.EXPECT().GetDeviceDefinitionByID(gomock.Any(), dd.DeviceDefinitionId).Return(dd, nil) + decoy := &models.Template{ + TemplateName: "protocol-powertrain-template-decoy", + Version: "1.0", + Protocol: models.CanProtocolTypeCAN29_500, + Powertrain: "PHEV", + } + err := decoy.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) + // Insert template into the database + template := &models.Template{ + TemplateName: "protocol-powertrain-template", + Version: "1.0", + Protocol: models.CanProtocolTypeCAN29_500, + Powertrain: "HEV", + } + err = template.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) - c := NewDeviceConfigController(&config.Settings{Port: "3000", DeploymentURL: "http://localhost:3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) + // Create a mocked user device + mockedUserDevice := &pb.UserDevice{ + Id: ksuid.New().String(), + UserId: ksuid.New().String(), + DeviceDefinitionId: "non-existing-def-id", + CANProtocol: models.CanProtocolTypeCAN29_500, + PowerTrainType: "HEV", + } - app := fiber.New() - app.Get("/config-urls/:vin", c.GetConfigURLsFromVIN) + vehicleMake := "Ford" + vehicleModel := "Mustang" + vehicleYear := 2021 - // Act: make the request - request := test.BuildRequest("GET", "/config-urls/"+vin, "") - response, err := app.Test(request) + // Act + fetchedTemplate, err := c.selectAndFetchTemplate(ctx, mockedUserDevice, vehicleMake, vehicleModel, vehicleYear) + + // Assert require.NoError(t, err) + assert.NotNil(t, fetchedTemplate) + assert.Equal(t, template.TemplateName, fetchedTemplate.TemplateName) - body, _ := io.ReadAll(response.Body) + // Teardown + test.TruncateTables(pdb.DBS().Writer.DB, t) +} - // Assert: check the results - assert.Equal(t, fiber.StatusOK, response.StatusCode, "response body: "+string(body)) +func TestSelectAndFetchTemplate_Default(t *testing.T) { + // Arrange + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() - var receivedResp DeviceConfigResponse - err = json.Unmarshal(body, &receivedResp) - assert.NoError(t, err) + logger := zerolog.New(os.Stdout).With(). + Timestamp(). + Str("app", "vehicle-signal-decoding"). + Logger() - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/pids", template.TemplateName), receivedResp.PidURL) - assert.Equal(t, fmt.Sprintf("http://localhost:3000/v1/device-config/%s/device-settings", template.TemplateName), receivedResp.DeviceSettingURL) - assert.Equal(t, "", receivedResp.DbcURL) + ctx := context.Background() - assert.Equal(t, template.Version, receivedResp.Version) + pdb, container := test.StartContainerDatabase(ctx, t, migrationsDirRelPath) + defer func() { + if err := container.Terminate(ctx); err != nil { + t.Fatal(err) + } + }() + + mockUserDeviceSvc := mock_services.NewMockUserDeviceService(mockCtrl) + mockDeviceDefSvc := mock_services.NewMockDeviceDefinitionsService(mockCtrl) + c := NewDeviceConfigController(&config.Settings{Port: "3000"}, &logger, pdb.DBS().Reader.DB, mockUserDeviceSvc, mockDeviceDefSvc) + + // decoy + nonDefaultTmpl := &models.Template{ + TemplateName: "some-template-special", + Version: "1.0", + Protocol: "CAN11_500", + Powertrain: "ICE", + } + err := nonDefaultTmpl.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) + // Insert a default template into the database + defaultTemplate := &models.Template{ + TemplateName: "default-some-template", + Version: "1.0", + Protocol: "CAN11_500", + Powertrain: "ICE", + } + err = defaultTemplate.Insert(ctx, pdb.DBS().Writer, boil.Infer()) + require.NoError(t, err) + + // Create a mocked user device that does not match any existing definitions, vehicles, or powertrain/protocol + mockedUserDevice := &pb.UserDevice{ + Id: ksuid.New().String(), + UserId: ksuid.New().String(), + DeviceDefinitionId: "non-existing-def-id", + CANProtocol: models.CanProtocolTypeCAN29_500, + PowerTrainType: "HEV", + } + + vehicleMake := "NonExistingMake" + vehicleModel := "NonExistingModel" + vehicleYear := 2010 + + // Act + fetchedTemplate, err := c.selectAndFetchTemplate(ctx, mockedUserDevice, vehicleMake, vehicleModel, vehicleYear) + + // Assert + require.NoError(t, err) + assert.NotNil(t, fetchedTemplate) + assert.Equal(t, defaultTemplate.TemplateName, fetchedTemplate.TemplateName) - // Teardown: cleanup after test test.TruncateTables(pdb.DBS().Writer.DB, t) } + +func Test_modelMatch(t *testing.T) { + tests := []struct { + name string + modelList types.StringArray + modelName string + want bool + }{ + { + name: "match found", + modelList: types.StringArray{"falcon", "model-x"}, + modelName: "model-x", + want: true, + }, + { + name: "no match found", + modelList: types.StringArray{"falcon", "model-x"}, + modelName: "model-y", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, modelMatch(tt.modelList, tt.modelName), "modelMatch(%v, %v)", tt.modelList, tt.modelName) + }) + } +} diff --git a/internal/core/services/device_definitions_service.go b/internal/core/services/device_definitions_service.go index 219a22e4..7a85da77 100644 --- a/internal/core/services/device_definitions_service.go +++ b/internal/core/services/device_definitions_service.go @@ -16,7 +16,6 @@ import ( type DeviceDefinitionsService interface { GetDeviceDefinitionByID(ctx context.Context, id string) (*p_grpc.GetDeviceDefinitionItemResponse, error) DecodeVIN(ctx context.Context, vin string) (*p_grpc.DecodeVinResponse, error) - // Add other methods as required. } type deviceDefinitionsService struct { diff --git a/internal/infrastructure/db/migrations/20231113225121_create_template_dd.sql b/internal/infrastructure/db/migrations/20231113225121_create_template_dd.sql new file mode 100644 index 00000000..87fc92f2 --- /dev/null +++ b/internal/infrastructure/db/migrations/20231113225121_create_template_dd.sql @@ -0,0 +1,25 @@ +-- +goose Up +-- +goose StatementBegin +SELECT 'up SQL query'; + +CREATE TABLE IF NOT EXISTS template_device_definitions ( + id BIGSERIAL, + device_definition_id char(27) NOT NULL, + device_style_id char(27), + template_name TEXT REFERENCES templates(template_name) NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT template_device_definitions_pkey PRIMARY KEY (id), + CONSTRAINT device_def_unique UNIQUE (device_definition_id, template_name, device_style_id) + ); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +SELECT 'down SQL query'; + +DROP TABLE IF EXISTS template_device_definitions; + +-- +goose StatementEnd diff --git a/internal/infrastructure/db/models/boil_queries.go b/internal/infrastructure/db/models/boil_queries.go index c4481975..7cc7c3d1 100644 --- a/internal/infrastructure/db/models/boil_queries.go +++ b/internal/infrastructure/db/models/boil_queries.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/boil_table_names.go b/internal/infrastructure/db/models/boil_table_names.go index f246e36b..924b1464 100644 --- a/internal/infrastructure/db/models/boil_table_names.go +++ b/internal/infrastructure/db/models/boil_table_names.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,6 +9,7 @@ var TableNames = struct { DeviceSettings string PidConfigs string SerialToTemplateOverrides string + TemplateDeviceDefinitions string TemplateTypes string TemplateVehicles string Templates string @@ -19,6 +20,7 @@ var TableNames = struct { DeviceSettings: "device_settings", PidConfigs: "pid_configs", SerialToTemplateOverrides: "serial_to_template_overrides", + TemplateDeviceDefinitions: "template_device_definitions", TemplateTypes: "template_types", TemplateVehicles: "template_vehicles", Templates: "templates", diff --git a/internal/infrastructure/db/models/boil_types.go b/internal/infrastructure/db/models/boil_types.go index 85a4dfe0..19c3d099 100644 --- a/internal/infrastructure/db/models/boil_types.go +++ b/internal/infrastructure/db/models/boil_types.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/boil_view_names.go b/internal/infrastructure/db/models/boil_view_names.go index 6b11f594..9f42d9e3 100644 --- a/internal/infrastructure/db/models/boil_view_names.go +++ b/internal/infrastructure/db/models/boil_view_names.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/dbc_codes.go b/internal/infrastructure/db/models/dbc_codes.go index f8d95502..b8276b9a 100644 --- a/internal/infrastructure/db/models/dbc_codes.go +++ b/internal/infrastructure/db/models/dbc_codes.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -86,12 +86,16 @@ var DBCCodeTableColumns = struct { type whereHelperstring struct{ field string } -func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } -func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } -func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } -func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } -func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } -func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) LIKE(x string) qm.QueryMod { return qm.Where(w.field+" LIKE ?", x) } +func (w whereHelperstring) NLIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT LIKE ?", x) } +func (w whereHelperstring) ILIKE(x string) qm.QueryMod { return qm.Where(w.field+" ILIKE ?", x) } +func (w whereHelperstring) NILIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT ILIKE ?", x) } func (w whereHelperstring) IN(slice []string) qm.QueryMod { values := make([]interface{}, 0, len(slice)) for _, value := range slice { @@ -127,6 +131,18 @@ func (w whereHelpernull_String) GT(x null.String) qm.QueryMod { func (w whereHelpernull_String) GTE(x null.String) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelpernull_String) LIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" LIKE ?", x) +} +func (w whereHelpernull_String) NLIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT LIKE ?", x) +} +func (w whereHelpernull_String) ILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" ILIKE ?", x) +} +func (w whereHelpernull_String) NILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT ILIKE ?", x) +} func (w whereHelpernull_String) IN(slice []string) qm.QueryMod { values := make([]interface{}, 0, len(slice)) for _, value := range slice { diff --git a/internal/infrastructure/db/models/dbc_files.go b/internal/infrastructure/db/models/dbc_files.go index 8f1f04fa..fbe271fb 100644 --- a/internal/infrastructure/db/models/dbc_files.go +++ b/internal/infrastructure/db/models/dbc_files.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/device_settings.go b/internal/infrastructure/db/models/device_settings.go index dd60ead7..0b9ad205 100644 --- a/internal/infrastructure/db/models/device_settings.go +++ b/internal/infrastructure/db/models/device_settings.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/pid_configs.go b/internal/infrastructure/db/models/pid_configs.go index c5d7b29d..cd2e9039 100644 --- a/internal/infrastructure/db/models/pid_configs.go +++ b/internal/infrastructure/db/models/pid_configs.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -24,77 +24,82 @@ import ( // PidConfig is an object representing the database table. type PidConfig struct { - ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` - TemplateName string `boil:"template_name" json:"template_name" toml:"template_name" yaml:"template_name"` - Header []byte `boil:"header" json:"header" toml:"header" yaml:"header"` - Mode []byte `boil:"mode" json:"mode" toml:"mode" yaml:"mode"` - Pid []byte `boil:"pid" json:"pid" toml:"pid" yaml:"pid"` - Formula string `boil:"formula" json:"formula" toml:"formula" yaml:"formula"` - IntervalSeconds int `boil:"interval_seconds" json:"interval_seconds" toml:"interval_seconds" yaml:"interval_seconds"` - Protocol string `boil:"protocol" json:"protocol" toml:"protocol" yaml:"protocol"` - CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` - UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` - SignalName string `boil:"signal_name" json:"signal_name" toml:"signal_name" yaml:"signal_name"` - BytesReturned null.Int16 `boil:"bytes_returned" json:"bytes_returned,omitempty" toml:"bytes_returned" yaml:"bytes_returned,omitempty"` + ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` + TemplateName string `boil:"template_name" json:"template_name" toml:"template_name" yaml:"template_name"` + Header []byte `boil:"header" json:"header" toml:"header" yaml:"header"` + Mode []byte `boil:"mode" json:"mode" toml:"mode" yaml:"mode"` + Pid []byte `boil:"pid" json:"pid" toml:"pid" yaml:"pid"` + Formula string `boil:"formula" json:"formula" toml:"formula" yaml:"formula"` + IntervalSeconds int `boil:"interval_seconds" json:"interval_seconds" toml:"interval_seconds" yaml:"interval_seconds"` + Protocol string `boil:"protocol" json:"protocol" toml:"protocol" yaml:"protocol"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + SignalName string `boil:"signal_name" json:"signal_name" toml:"signal_name" yaml:"signal_name"` + BytesReturned null.Int16 `boil:"bytes_returned" json:"bytes_returned,omitempty" toml:"bytes_returned" yaml:"bytes_returned,omitempty"` + MessageColumnPosition null.Int `boil:"message_column_position" json:"message_column_position,omitempty" toml:"message_column_position" yaml:"message_column_position,omitempty"` R *pidConfigR `boil:"-" json:"-" toml:"-" yaml:"-"` L pidConfigL `boil:"-" json:"-" toml:"-" yaml:"-"` } var PidConfigColumns = struct { - ID string - TemplateName string - Header string - Mode string - Pid string - Formula string - IntervalSeconds string - Protocol string - CreatedAt string - UpdatedAt string - SignalName string - BytesReturned string + ID string + TemplateName string + Header string + Mode string + Pid string + Formula string + IntervalSeconds string + Protocol string + CreatedAt string + UpdatedAt string + SignalName string + BytesReturned string + MessageColumnPosition string }{ - ID: "id", - TemplateName: "template_name", - Header: "header", - Mode: "mode", - Pid: "pid", - Formula: "formula", - IntervalSeconds: "interval_seconds", - Protocol: "protocol", - CreatedAt: "created_at", - UpdatedAt: "updated_at", - SignalName: "signal_name", - BytesReturned: "bytes_returned", + ID: "id", + TemplateName: "template_name", + Header: "header", + Mode: "mode", + Pid: "pid", + Formula: "formula", + IntervalSeconds: "interval_seconds", + Protocol: "protocol", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + SignalName: "signal_name", + BytesReturned: "bytes_returned", + MessageColumnPosition: "message_column_position", } var PidConfigTableColumns = struct { - ID string - TemplateName string - Header string - Mode string - Pid string - Formula string - IntervalSeconds string - Protocol string - CreatedAt string - UpdatedAt string - SignalName string - BytesReturned string + ID string + TemplateName string + Header string + Mode string + Pid string + Formula string + IntervalSeconds string + Protocol string + CreatedAt string + UpdatedAt string + SignalName string + BytesReturned string + MessageColumnPosition string }{ - ID: "pid_configs.id", - TemplateName: "pid_configs.template_name", - Header: "pid_configs.header", - Mode: "pid_configs.mode", - Pid: "pid_configs.pid", - Formula: "pid_configs.formula", - IntervalSeconds: "pid_configs.interval_seconds", - Protocol: "pid_configs.protocol", - CreatedAt: "pid_configs.created_at", - UpdatedAt: "pid_configs.updated_at", - SignalName: "pid_configs.signal_name", - BytesReturned: "pid_configs.bytes_returned", + ID: "pid_configs.id", + TemplateName: "pid_configs.template_name", + Header: "pid_configs.header", + Mode: "pid_configs.mode", + Pid: "pid_configs.pid", + Formula: "pid_configs.formula", + IntervalSeconds: "pid_configs.interval_seconds", + Protocol: "pid_configs.protocol", + CreatedAt: "pid_configs.created_at", + UpdatedAt: "pid_configs.updated_at", + SignalName: "pid_configs.signal_name", + BytesReturned: "pid_configs.bytes_returned", + MessageColumnPosition: "pid_configs.message_column_position", } // Generated where @@ -147,31 +152,33 @@ func (w whereHelpernull_Int16) IsNull() qm.QueryMod { return qmhelper.WhereIs func (w whereHelpernull_Int16) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } var PidConfigWhere = struct { - ID whereHelperint64 - TemplateName whereHelperstring - Header whereHelper__byte - Mode whereHelper__byte - Pid whereHelper__byte - Formula whereHelperstring - IntervalSeconds whereHelperint - Protocol whereHelperstring - CreatedAt whereHelpertime_Time - UpdatedAt whereHelpertime_Time - SignalName whereHelperstring - BytesReturned whereHelpernull_Int16 + ID whereHelperint64 + TemplateName whereHelperstring + Header whereHelper__byte + Mode whereHelper__byte + Pid whereHelper__byte + Formula whereHelperstring + IntervalSeconds whereHelperint + Protocol whereHelperstring + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + SignalName whereHelperstring + BytesReturned whereHelpernull_Int16 + MessageColumnPosition whereHelpernull_Int }{ - ID: whereHelperint64{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"id\""}, - TemplateName: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"template_name\""}, - Header: whereHelper__byte{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"header\""}, - Mode: whereHelper__byte{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"mode\""}, - Pid: whereHelper__byte{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"pid\""}, - Formula: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"formula\""}, - IntervalSeconds: whereHelperint{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"interval_seconds\""}, - Protocol: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"protocol\""}, - CreatedAt: whereHelpertime_Time{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"created_at\""}, - UpdatedAt: whereHelpertime_Time{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"updated_at\""}, - SignalName: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"signal_name\""}, - BytesReturned: whereHelpernull_Int16{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"bytes_returned\""}, + ID: whereHelperint64{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"id\""}, + TemplateName: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"template_name\""}, + Header: whereHelper__byte{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"header\""}, + Mode: whereHelper__byte{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"mode\""}, + Pid: whereHelper__byte{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"pid\""}, + Formula: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"formula\""}, + IntervalSeconds: whereHelperint{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"interval_seconds\""}, + Protocol: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"protocol\""}, + CreatedAt: whereHelpertime_Time{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"updated_at\""}, + SignalName: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"signal_name\""}, + BytesReturned: whereHelpernull_Int16{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"bytes_returned\""}, + MessageColumnPosition: whereHelpernull_Int{field: "\"vehicle_signal_decoding_api\".\"pid_configs\".\"message_column_position\""}, } // PidConfigRels is where relationship names are stored. @@ -202,9 +209,9 @@ func (r *pidConfigR) GetTemplateNameTemplate() *Template { type pidConfigL struct{} var ( - pidConfigAllColumns = []string{"id", "template_name", "header", "mode", "pid", "formula", "interval_seconds", "protocol", "created_at", "updated_at", "signal_name", "bytes_returned"} + pidConfigAllColumns = []string{"id", "template_name", "header", "mode", "pid", "formula", "interval_seconds", "protocol", "created_at", "updated_at", "signal_name", "bytes_returned", "message_column_position"} pidConfigColumnsWithoutDefault = []string{"template_name", "pid", "formula", "interval_seconds", "protocol", "signal_name"} - pidConfigColumnsWithDefault = []string{"id", "header", "mode", "created_at", "updated_at", "bytes_returned"} + pidConfigColumnsWithDefault = []string{"id", "header", "mode", "created_at", "updated_at", "bytes_returned", "message_column_position"} pidConfigPrimaryKeyColumns = []string{"id"} pidConfigGeneratedColumns = []string{} ) diff --git a/internal/infrastructure/db/models/psql_upsert.go b/internal/infrastructure/db/models/psql_upsert.go index 1817cc98..d417a129 100644 --- a/internal/infrastructure/db/models/psql_upsert.go +++ b/internal/infrastructure/db/models/psql_upsert.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -34,14 +34,18 @@ func buildUpsertQueryPostgres(dia drivers.Dialect, tableName string, updateOnCon columns, ) + buf.WriteByte('(') + buf.WriteString(strings.Join(conflict, ", ")) + if !updateOnConflict || len(update) == 0 { - buf.WriteString("DO NOTHING") + buf.WriteString(") DO NOTHING") } else { - buf.WriteByte('(') - buf.WriteString(strings.Join(conflict, ", ")) buf.WriteString(") DO UPDATE SET ") for i, v := range update { + if len(v) == 0 { + continue + } if i != 0 { buf.WriteByte(',') } diff --git a/internal/infrastructure/db/models/serial_to_template_overrides.go b/internal/infrastructure/db/models/serial_to_template_overrides.go index d941b523..fe4d23e6 100644 --- a/internal/infrastructure/db/models/serial_to_template_overrides.go +++ b/internal/infrastructure/db/models/serial_to_template_overrides.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/template_device_definitions.go b/internal/infrastructure/db/models/template_device_definitions.go new file mode 100644 index 00000000..1e3abb36 --- /dev/null +++ b/internal/infrastructure/db/models/template_device_definitions.go @@ -0,0 +1,1137 @@ +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// TemplateDeviceDefinition is an object representing the database table. +type TemplateDeviceDefinition struct { + ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` + DeviceDefinitionID string `boil:"device_definition_id" json:"device_definition_id" toml:"device_definition_id" yaml:"device_definition_id"` + DeviceStyleID null.String `boil:"device_style_id" json:"device_style_id,omitempty" toml:"device_style_id" yaml:"device_style_id,omitempty"` + TemplateName string `boil:"template_name" json:"template_name" toml:"template_name" yaml:"template_name"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + + R *templateDeviceDefinitionR `boil:"-" json:"-" toml:"-" yaml:"-"` + L templateDeviceDefinitionL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var TemplateDeviceDefinitionColumns = struct { + ID string + DeviceDefinitionID string + DeviceStyleID string + TemplateName string + CreatedAt string + UpdatedAt string +}{ + ID: "id", + DeviceDefinitionID: "device_definition_id", + DeviceStyleID: "device_style_id", + TemplateName: "template_name", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +var TemplateDeviceDefinitionTableColumns = struct { + ID string + DeviceDefinitionID string + DeviceStyleID string + TemplateName string + CreatedAt string + UpdatedAt string +}{ + ID: "template_device_definitions.id", + DeviceDefinitionID: "template_device_definitions.device_definition_id", + DeviceStyleID: "template_device_definitions.device_style_id", + TemplateName: "template_device_definitions.template_name", + CreatedAt: "template_device_definitions.created_at", + UpdatedAt: "template_device_definitions.updated_at", +} + +// Generated where + +var TemplateDeviceDefinitionWhere = struct { + ID whereHelperint64 + DeviceDefinitionID whereHelperstring + DeviceStyleID whereHelpernull_String + TemplateName whereHelperstring + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time +}{ + ID: whereHelperint64{field: "\"vehicle_signal_decoding_api\".\"template_device_definitions\".\"id\""}, + DeviceDefinitionID: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"template_device_definitions\".\"device_definition_id\""}, + DeviceStyleID: whereHelpernull_String{field: "\"vehicle_signal_decoding_api\".\"template_device_definitions\".\"device_style_id\""}, + TemplateName: whereHelperstring{field: "\"vehicle_signal_decoding_api\".\"template_device_definitions\".\"template_name\""}, + CreatedAt: whereHelpertime_Time{field: "\"vehicle_signal_decoding_api\".\"template_device_definitions\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"vehicle_signal_decoding_api\".\"template_device_definitions\".\"updated_at\""}, +} + +// TemplateDeviceDefinitionRels is where relationship names are stored. +var TemplateDeviceDefinitionRels = struct { + TemplateNameTemplate string +}{ + TemplateNameTemplate: "TemplateNameTemplate", +} + +// templateDeviceDefinitionR is where relationships are stored. +type templateDeviceDefinitionR struct { + TemplateNameTemplate *Template `boil:"TemplateNameTemplate" json:"TemplateNameTemplate" toml:"TemplateNameTemplate" yaml:"TemplateNameTemplate"` +} + +// NewStruct creates a new relationship struct +func (*templateDeviceDefinitionR) NewStruct() *templateDeviceDefinitionR { + return &templateDeviceDefinitionR{} +} + +func (r *templateDeviceDefinitionR) GetTemplateNameTemplate() *Template { + if r == nil { + return nil + } + return r.TemplateNameTemplate +} + +// templateDeviceDefinitionL is where Load methods for each relationship are stored. +type templateDeviceDefinitionL struct{} + +var ( + templateDeviceDefinitionAllColumns = []string{"id", "device_definition_id", "device_style_id", "template_name", "created_at", "updated_at"} + templateDeviceDefinitionColumnsWithoutDefault = []string{"device_definition_id", "template_name"} + templateDeviceDefinitionColumnsWithDefault = []string{"id", "device_style_id", "created_at", "updated_at"} + templateDeviceDefinitionPrimaryKeyColumns = []string{"id"} + templateDeviceDefinitionGeneratedColumns = []string{} +) + +type ( + // TemplateDeviceDefinitionSlice is an alias for a slice of pointers to TemplateDeviceDefinition. + // This should almost always be used instead of []TemplateDeviceDefinition. + TemplateDeviceDefinitionSlice []*TemplateDeviceDefinition + // TemplateDeviceDefinitionHook is the signature for custom TemplateDeviceDefinition hook methods + TemplateDeviceDefinitionHook func(context.Context, boil.ContextExecutor, *TemplateDeviceDefinition) error + + templateDeviceDefinitionQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + templateDeviceDefinitionType = reflect.TypeOf(&TemplateDeviceDefinition{}) + templateDeviceDefinitionMapping = queries.MakeStructMapping(templateDeviceDefinitionType) + templateDeviceDefinitionPrimaryKeyMapping, _ = queries.BindMapping(templateDeviceDefinitionType, templateDeviceDefinitionMapping, templateDeviceDefinitionPrimaryKeyColumns) + templateDeviceDefinitionInsertCacheMut sync.RWMutex + templateDeviceDefinitionInsertCache = make(map[string]insertCache) + templateDeviceDefinitionUpdateCacheMut sync.RWMutex + templateDeviceDefinitionUpdateCache = make(map[string]updateCache) + templateDeviceDefinitionUpsertCacheMut sync.RWMutex + templateDeviceDefinitionUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var templateDeviceDefinitionAfterSelectHooks []TemplateDeviceDefinitionHook + +var templateDeviceDefinitionBeforeInsertHooks []TemplateDeviceDefinitionHook +var templateDeviceDefinitionAfterInsertHooks []TemplateDeviceDefinitionHook + +var templateDeviceDefinitionBeforeUpdateHooks []TemplateDeviceDefinitionHook +var templateDeviceDefinitionAfterUpdateHooks []TemplateDeviceDefinitionHook + +var templateDeviceDefinitionBeforeDeleteHooks []TemplateDeviceDefinitionHook +var templateDeviceDefinitionAfterDeleteHooks []TemplateDeviceDefinitionHook + +var templateDeviceDefinitionBeforeUpsertHooks []TemplateDeviceDefinitionHook +var templateDeviceDefinitionAfterUpsertHooks []TemplateDeviceDefinitionHook + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *TemplateDeviceDefinition) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *TemplateDeviceDefinition) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *TemplateDeviceDefinition) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *TemplateDeviceDefinition) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *TemplateDeviceDefinition) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *TemplateDeviceDefinition) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *TemplateDeviceDefinition) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *TemplateDeviceDefinition) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *TemplateDeviceDefinition) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range templateDeviceDefinitionAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddTemplateDeviceDefinitionHook registers your hook function for all future operations. +func AddTemplateDeviceDefinitionHook(hookPoint boil.HookPoint, templateDeviceDefinitionHook TemplateDeviceDefinitionHook) { + switch hookPoint { + case boil.AfterSelectHook: + templateDeviceDefinitionAfterSelectHooks = append(templateDeviceDefinitionAfterSelectHooks, templateDeviceDefinitionHook) + case boil.BeforeInsertHook: + templateDeviceDefinitionBeforeInsertHooks = append(templateDeviceDefinitionBeforeInsertHooks, templateDeviceDefinitionHook) + case boil.AfterInsertHook: + templateDeviceDefinitionAfterInsertHooks = append(templateDeviceDefinitionAfterInsertHooks, templateDeviceDefinitionHook) + case boil.BeforeUpdateHook: + templateDeviceDefinitionBeforeUpdateHooks = append(templateDeviceDefinitionBeforeUpdateHooks, templateDeviceDefinitionHook) + case boil.AfterUpdateHook: + templateDeviceDefinitionAfterUpdateHooks = append(templateDeviceDefinitionAfterUpdateHooks, templateDeviceDefinitionHook) + case boil.BeforeDeleteHook: + templateDeviceDefinitionBeforeDeleteHooks = append(templateDeviceDefinitionBeforeDeleteHooks, templateDeviceDefinitionHook) + case boil.AfterDeleteHook: + templateDeviceDefinitionAfterDeleteHooks = append(templateDeviceDefinitionAfterDeleteHooks, templateDeviceDefinitionHook) + case boil.BeforeUpsertHook: + templateDeviceDefinitionBeforeUpsertHooks = append(templateDeviceDefinitionBeforeUpsertHooks, templateDeviceDefinitionHook) + case boil.AfterUpsertHook: + templateDeviceDefinitionAfterUpsertHooks = append(templateDeviceDefinitionAfterUpsertHooks, templateDeviceDefinitionHook) + } +} + +// One returns a single templateDeviceDefinition record from the query. +func (q templateDeviceDefinitionQuery) One(ctx context.Context, exec boil.ContextExecutor) (*TemplateDeviceDefinition, error) { + o := &TemplateDeviceDefinition{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for template_device_definitions") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all TemplateDeviceDefinition records from the query. +func (q templateDeviceDefinitionQuery) All(ctx context.Context, exec boil.ContextExecutor) (TemplateDeviceDefinitionSlice, error) { + var o []*TemplateDeviceDefinition + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to TemplateDeviceDefinition slice") + } + + if len(templateDeviceDefinitionAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all TemplateDeviceDefinition records in the query. +func (q templateDeviceDefinitionQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count template_device_definitions rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q templateDeviceDefinitionQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if template_device_definitions exists") + } + + return count > 0, nil +} + +// TemplateNameTemplate pointed to by the foreign key. +func (o *TemplateDeviceDefinition) TemplateNameTemplate(mods ...qm.QueryMod) templateQuery { + queryMods := []qm.QueryMod{ + qm.Where("\"template_name\" = ?", o.TemplateName), + } + + queryMods = append(queryMods, mods...) + + return Templates(queryMods...) +} + +// LoadTemplateNameTemplate allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for an N-1 relationship. +func (templateDeviceDefinitionL) LoadTemplateNameTemplate(ctx context.Context, e boil.ContextExecutor, singular bool, maybeTemplateDeviceDefinition interface{}, mods queries.Applicator) error { + var slice []*TemplateDeviceDefinition + var object *TemplateDeviceDefinition + + if singular { + var ok bool + object, ok = maybeTemplateDeviceDefinition.(*TemplateDeviceDefinition) + if !ok { + object = new(TemplateDeviceDefinition) + ok = queries.SetFromEmbeddedStruct(&object, &maybeTemplateDeviceDefinition) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeTemplateDeviceDefinition)) + } + } + } else { + s, ok := maybeTemplateDeviceDefinition.(*[]*TemplateDeviceDefinition) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeTemplateDeviceDefinition) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeTemplateDeviceDefinition)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = &templateDeviceDefinitionR{} + } + args = append(args, object.TemplateName) + + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = &templateDeviceDefinitionR{} + } + + for _, a := range args { + if a == obj.TemplateName { + continue Outer + } + } + + args = append(args, obj.TemplateName) + + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`vehicle_signal_decoding_api.templates`), + qm.WhereIn(`vehicle_signal_decoding_api.templates.template_name in ?`, args...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load Template") + } + + var resultSlice []*Template + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice Template") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for templates") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for templates") + } + + if len(templateAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + + if len(resultSlice) == 0 { + return nil + } + + if singular { + foreign := resultSlice[0] + object.R.TemplateNameTemplate = foreign + if foreign.R == nil { + foreign.R = &templateR{} + } + foreign.R.TemplateNameTemplateDeviceDefinitions = append(foreign.R.TemplateNameTemplateDeviceDefinitions, object) + return nil + } + + for _, local := range slice { + for _, foreign := range resultSlice { + if local.TemplateName == foreign.TemplateName { + local.R.TemplateNameTemplate = foreign + if foreign.R == nil { + foreign.R = &templateR{} + } + foreign.R.TemplateNameTemplateDeviceDefinitions = append(foreign.R.TemplateNameTemplateDeviceDefinitions, local) + break + } + } + } + + return nil +} + +// SetTemplateNameTemplate of the templateDeviceDefinition to the related item. +// Sets o.R.TemplateNameTemplate to related. +// Adds o to related.R.TemplateNameTemplateDeviceDefinitions. +func (o *TemplateDeviceDefinition) SetTemplateNameTemplate(ctx context.Context, exec boil.ContextExecutor, insert bool, related *Template) error { + var err error + if insert { + if err = related.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + + updateQuery := fmt.Sprintf( + "UPDATE \"vehicle_signal_decoding_api\".\"template_device_definitions\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"template_name"}), + strmangle.WhereClause("\"", "\"", 2, templateDeviceDefinitionPrimaryKeyColumns), + ) + values := []interface{}{related.TemplateName, o.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + o.TemplateName = related.TemplateName + if o.R == nil { + o.R = &templateDeviceDefinitionR{ + TemplateNameTemplate: related, + } + } else { + o.R.TemplateNameTemplate = related + } + + if related.R == nil { + related.R = &templateR{ + TemplateNameTemplateDeviceDefinitions: TemplateDeviceDefinitionSlice{o}, + } + } else { + related.R.TemplateNameTemplateDeviceDefinitions = append(related.R.TemplateNameTemplateDeviceDefinitions, o) + } + + return nil +} + +// TemplateDeviceDefinitions retrieves all the records using an executor. +func TemplateDeviceDefinitions(mods ...qm.QueryMod) templateDeviceDefinitionQuery { + mods = append(mods, qm.From("\"vehicle_signal_decoding_api\".\"template_device_definitions\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"vehicle_signal_decoding_api\".\"template_device_definitions\".*"}) + } + + return templateDeviceDefinitionQuery{q} +} + +// FindTemplateDeviceDefinition retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindTemplateDeviceDefinition(ctx context.Context, exec boil.ContextExecutor, iD int64, selectCols ...string) (*TemplateDeviceDefinition, error) { + templateDeviceDefinitionObj := &TemplateDeviceDefinition{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"vehicle_signal_decoding_api\".\"template_device_definitions\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, templateDeviceDefinitionObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from template_device_definitions") + } + + if err = templateDeviceDefinitionObj.doAfterSelectHooks(ctx, exec); err != nil { + return templateDeviceDefinitionObj, err + } + + return templateDeviceDefinitionObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *TemplateDeviceDefinition) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no template_device_definitions provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(templateDeviceDefinitionColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + templateDeviceDefinitionInsertCacheMut.RLock() + cache, cached := templateDeviceDefinitionInsertCache[key] + templateDeviceDefinitionInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + templateDeviceDefinitionAllColumns, + templateDeviceDefinitionColumnsWithDefault, + templateDeviceDefinitionColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(templateDeviceDefinitionType, templateDeviceDefinitionMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(templateDeviceDefinitionType, templateDeviceDefinitionMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"vehicle_signal_decoding_api\".\"template_device_definitions\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"vehicle_signal_decoding_api\".\"template_device_definitions\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into template_device_definitions") + } + + if !cached { + templateDeviceDefinitionInsertCacheMut.Lock() + templateDeviceDefinitionInsertCache[key] = cache + templateDeviceDefinitionInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the TemplateDeviceDefinition. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *TemplateDeviceDefinition) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + templateDeviceDefinitionUpdateCacheMut.RLock() + cache, cached := templateDeviceDefinitionUpdateCache[key] + templateDeviceDefinitionUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + templateDeviceDefinitionAllColumns, + templateDeviceDefinitionPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update template_device_definitions, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"vehicle_signal_decoding_api\".\"template_device_definitions\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, templateDeviceDefinitionPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(templateDeviceDefinitionType, templateDeviceDefinitionMapping, append(wl, templateDeviceDefinitionPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update template_device_definitions row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for template_device_definitions") + } + + if !cached { + templateDeviceDefinitionUpdateCacheMut.Lock() + templateDeviceDefinitionUpdateCache[key] = cache + templateDeviceDefinitionUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q templateDeviceDefinitionQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for template_device_definitions") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for template_device_definitions") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o TemplateDeviceDefinitionSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), templateDeviceDefinitionPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"vehicle_signal_decoding_api\".\"template_device_definitions\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, templateDeviceDefinitionPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in templateDeviceDefinition slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all templateDeviceDefinition") + } + return rowsAff, nil +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *TemplateDeviceDefinition) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns) error { + if o == nil { + return errors.New("models: no template_device_definitions provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + if err := o.doBeforeUpsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(templateDeviceDefinitionColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + templateDeviceDefinitionUpsertCacheMut.RLock() + cache, cached := templateDeviceDefinitionUpsertCache[key] + templateDeviceDefinitionUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, ret := insertColumns.InsertColumnSet( + templateDeviceDefinitionAllColumns, + templateDeviceDefinitionColumnsWithDefault, + templateDeviceDefinitionColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + templateDeviceDefinitionAllColumns, + templateDeviceDefinitionPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert template_device_definitions, could not build update column list") + } + + conflict := conflictColumns + if len(conflict) == 0 { + conflict = make([]string, len(templateDeviceDefinitionPrimaryKeyColumns)) + copy(conflict, templateDeviceDefinitionPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"vehicle_signal_decoding_api\".\"template_device_definitions\"", updateOnConflict, ret, update, conflict, insert) + + cache.valueMapping, err = queries.BindMapping(templateDeviceDefinitionType, templateDeviceDefinitionMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(templateDeviceDefinitionType, templateDeviceDefinitionMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert template_device_definitions") + } + + if !cached { + templateDeviceDefinitionUpsertCacheMut.Lock() + templateDeviceDefinitionUpsertCache[key] = cache + templateDeviceDefinitionUpsertCacheMut.Unlock() + } + + return o.doAfterUpsertHooks(ctx, exec) +} + +// Delete deletes a single TemplateDeviceDefinition record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *TemplateDeviceDefinition) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no TemplateDeviceDefinition provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), templateDeviceDefinitionPrimaryKeyMapping) + sql := "DELETE FROM \"vehicle_signal_decoding_api\".\"template_device_definitions\" WHERE \"id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from template_device_definitions") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for template_device_definitions") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q templateDeviceDefinitionQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no templateDeviceDefinitionQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from template_device_definitions") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for template_device_definitions") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o TemplateDeviceDefinitionSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(templateDeviceDefinitionBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), templateDeviceDefinitionPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"vehicle_signal_decoding_api\".\"template_device_definitions\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, templateDeviceDefinitionPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from templateDeviceDefinition slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for template_device_definitions") + } + + if len(templateDeviceDefinitionAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *TemplateDeviceDefinition) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindTemplateDeviceDefinition(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *TemplateDeviceDefinitionSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := TemplateDeviceDefinitionSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), templateDeviceDefinitionPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"vehicle_signal_decoding_api\".\"template_device_definitions\".* FROM \"vehicle_signal_decoding_api\".\"template_device_definitions\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, templateDeviceDefinitionPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in TemplateDeviceDefinitionSlice") + } + + *o = slice + + return nil +} + +// TemplateDeviceDefinitionExists checks if the TemplateDeviceDefinition row exists. +func TemplateDeviceDefinitionExists(ctx context.Context, exec boil.ContextExecutor, iD int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"vehicle_signal_decoding_api\".\"template_device_definitions\" where \"id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if template_device_definitions exists") + } + + return exists, nil +} + +// Exists checks if the TemplateDeviceDefinition row exists. +func (o *TemplateDeviceDefinition) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return TemplateDeviceDefinitionExists(ctx, exec, o.ID) +} diff --git a/internal/infrastructure/db/models/template_types.go b/internal/infrastructure/db/models/template_types.go index fe6de360..40c931e4 100644 --- a/internal/infrastructure/db/models/template_types.go +++ b/internal/infrastructure/db/models/template_types.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/template_vehicles.go b/internal/infrastructure/db/models/template_vehicles.go index 6a850e61..5b52c8b8 100644 --- a/internal/infrastructure/db/models/template_vehicles.go +++ b/internal/infrastructure/db/models/template_vehicles.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/internal/infrastructure/db/models/templates.go b/internal/infrastructure/db/models/templates.go index f70f2057..5b27b888 100644 --- a/internal/infrastructure/db/models/templates.go +++ b/internal/infrastructure/db/models/templates.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -101,26 +101,29 @@ var TemplateWhere = struct { // TemplateRels is where relationship names are stored. var TemplateRels = struct { - TemplateTemplateType string - TemplateNameDBCFile string - TemplateNameDeviceSetting string - TemplateNamePidConfigs string - TemplateNameTemplateVehicles string + TemplateTemplateType string + TemplateNameDBCFile string + TemplateNameDeviceSetting string + TemplateNamePidConfigs string + TemplateNameTemplateDeviceDefinitions string + TemplateNameTemplateVehicles string }{ - TemplateTemplateType: "TemplateTemplateType", - TemplateNameDBCFile: "TemplateNameDBCFile", - TemplateNameDeviceSetting: "TemplateNameDeviceSetting", - TemplateNamePidConfigs: "TemplateNamePidConfigs", - TemplateNameTemplateVehicles: "TemplateNameTemplateVehicles", + TemplateTemplateType: "TemplateTemplateType", + TemplateNameDBCFile: "TemplateNameDBCFile", + TemplateNameDeviceSetting: "TemplateNameDeviceSetting", + TemplateNamePidConfigs: "TemplateNamePidConfigs", + TemplateNameTemplateDeviceDefinitions: "TemplateNameTemplateDeviceDefinitions", + TemplateNameTemplateVehicles: "TemplateNameTemplateVehicles", } // templateR is where relationships are stored. type templateR struct { - TemplateTemplateType *TemplateType `boil:"TemplateTemplateType" json:"TemplateTemplateType" toml:"TemplateTemplateType" yaml:"TemplateTemplateType"` - TemplateNameDBCFile *DBCFile `boil:"TemplateNameDBCFile" json:"TemplateNameDBCFile" toml:"TemplateNameDBCFile" yaml:"TemplateNameDBCFile"` - TemplateNameDeviceSetting *DeviceSetting `boil:"TemplateNameDeviceSetting" json:"TemplateNameDeviceSetting" toml:"TemplateNameDeviceSetting" yaml:"TemplateNameDeviceSetting"` - TemplateNamePidConfigs PidConfigSlice `boil:"TemplateNamePidConfigs" json:"TemplateNamePidConfigs" toml:"TemplateNamePidConfigs" yaml:"TemplateNamePidConfigs"` - TemplateNameTemplateVehicles TemplateVehicleSlice `boil:"TemplateNameTemplateVehicles" json:"TemplateNameTemplateVehicles" toml:"TemplateNameTemplateVehicles" yaml:"TemplateNameTemplateVehicles"` + TemplateTemplateType *TemplateType `boil:"TemplateTemplateType" json:"TemplateTemplateType" toml:"TemplateTemplateType" yaml:"TemplateTemplateType"` + TemplateNameDBCFile *DBCFile `boil:"TemplateNameDBCFile" json:"TemplateNameDBCFile" toml:"TemplateNameDBCFile" yaml:"TemplateNameDBCFile"` + TemplateNameDeviceSetting *DeviceSetting `boil:"TemplateNameDeviceSetting" json:"TemplateNameDeviceSetting" toml:"TemplateNameDeviceSetting" yaml:"TemplateNameDeviceSetting"` + TemplateNamePidConfigs PidConfigSlice `boil:"TemplateNamePidConfigs" json:"TemplateNamePidConfigs" toml:"TemplateNamePidConfigs" yaml:"TemplateNamePidConfigs"` + TemplateNameTemplateDeviceDefinitions TemplateDeviceDefinitionSlice `boil:"TemplateNameTemplateDeviceDefinitions" json:"TemplateNameTemplateDeviceDefinitions" toml:"TemplateNameTemplateDeviceDefinitions" yaml:"TemplateNameTemplateDeviceDefinitions"` + TemplateNameTemplateVehicles TemplateVehicleSlice `boil:"TemplateNameTemplateVehicles" json:"TemplateNameTemplateVehicles" toml:"TemplateNameTemplateVehicles" yaml:"TemplateNameTemplateVehicles"` } // NewStruct creates a new relationship struct @@ -156,6 +159,13 @@ func (r *templateR) GetTemplateNamePidConfigs() PidConfigSlice { return r.TemplateNamePidConfigs } +func (r *templateR) GetTemplateNameTemplateDeviceDefinitions() TemplateDeviceDefinitionSlice { + if r == nil { + return nil + } + return r.TemplateNameTemplateDeviceDefinitions +} + func (r *templateR) GetTemplateNameTemplateVehicles() TemplateVehicleSlice { if r == nil { return nil @@ -499,6 +509,20 @@ func (o *Template) TemplateNamePidConfigs(mods ...qm.QueryMod) pidConfigQuery { return PidConfigs(queryMods...) } +// TemplateNameTemplateDeviceDefinitions retrieves all the template_device_definition's TemplateDeviceDefinitions with an executor via template_name column. +func (o *Template) TemplateNameTemplateDeviceDefinitions(mods ...qm.QueryMod) templateDeviceDefinitionQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.Where("\"vehicle_signal_decoding_api\".\"template_device_definitions\".\"template_name\"=?", o.TemplateName), + ) + + return TemplateDeviceDefinitions(queryMods...) +} + // TemplateNameTemplateVehicles retrieves all the template_vehicle's TemplateVehicles with an executor via template_name column. func (o *Template) TemplateNameTemplateVehicles(mods ...qm.QueryMod) templateVehicleQuery { var queryMods []qm.QueryMod @@ -985,6 +1009,120 @@ func (templateL) LoadTemplateNamePidConfigs(ctx context.Context, e boil.ContextE return nil } +// LoadTemplateNameTemplateDeviceDefinitions allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (templateL) LoadTemplateNameTemplateDeviceDefinitions(ctx context.Context, e boil.ContextExecutor, singular bool, maybeTemplate interface{}, mods queries.Applicator) error { + var slice []*Template + var object *Template + + if singular { + var ok bool + object, ok = maybeTemplate.(*Template) + if !ok { + object = new(Template) + ok = queries.SetFromEmbeddedStruct(&object, &maybeTemplate) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeTemplate)) + } + } + } else { + s, ok := maybeTemplate.(*[]*Template) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeTemplate) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeTemplate)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = &templateR{} + } + args = append(args, object.TemplateName) + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = &templateR{} + } + + for _, a := range args { + if a == obj.TemplateName { + continue Outer + } + } + + args = append(args, obj.TemplateName) + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`vehicle_signal_decoding_api.template_device_definitions`), + qm.WhereIn(`vehicle_signal_decoding_api.template_device_definitions.template_name in ?`, args...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load template_device_definitions") + } + + var resultSlice []*TemplateDeviceDefinition + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice template_device_definitions") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on template_device_definitions") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for template_device_definitions") + } + + if len(templateDeviceDefinitionAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.TemplateNameTemplateDeviceDefinitions = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = &templateDeviceDefinitionR{} + } + foreign.R.TemplateNameTemplate = object + } + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + if local.TemplateName == foreign.TemplateName { + local.R.TemplateNameTemplateDeviceDefinitions = append(local.R.TemplateNameTemplateDeviceDefinitions, foreign) + if foreign.R == nil { + foreign.R = &templateDeviceDefinitionR{} + } + foreign.R.TemplateNameTemplate = local + break + } + } + } + + return nil +} + // LoadTemplateNameTemplateVehicles allows an eager lookup of values, cached into the // loaded structs of the objects. This is for a 1-M or N-M relationship. func (templateL) LoadTemplateNameTemplateVehicles(ctx context.Context, e boil.ContextExecutor, singular bool, maybeTemplate interface{}, mods queries.Applicator) error { @@ -1332,6 +1470,59 @@ func (o *Template) AddTemplateNamePidConfigs(ctx context.Context, exec boil.Cont return nil } +// AddTemplateNameTemplateDeviceDefinitions adds the given related objects to the existing relationships +// of the template, optionally inserting them as new records. +// Appends related to o.R.TemplateNameTemplateDeviceDefinitions. +// Sets related.R.TemplateNameTemplate appropriately. +func (o *Template) AddTemplateNameTemplateDeviceDefinitions(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*TemplateDeviceDefinition) error { + var err error + for _, rel := range related { + if insert { + rel.TemplateName = o.TemplateName + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + updateQuery := fmt.Sprintf( + "UPDATE \"vehicle_signal_decoding_api\".\"template_device_definitions\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"template_name"}), + strmangle.WhereClause("\"", "\"", 2, templateDeviceDefinitionPrimaryKeyColumns), + ) + values := []interface{}{o.TemplateName, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + + rel.TemplateName = o.TemplateName + } + } + + if o.R == nil { + o.R = &templateR{ + TemplateNameTemplateDeviceDefinitions: related, + } + } else { + o.R.TemplateNameTemplateDeviceDefinitions = append(o.R.TemplateNameTemplateDeviceDefinitions, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = &templateDeviceDefinitionR{ + TemplateNameTemplate: o, + } + } else { + rel.R.TemplateNameTemplate = o + } + } + return nil +} + // AddTemplateNameTemplateVehicles adds the given related objects to the existing relationships // of the template, optionally inserting them as new records. // Appends related to o.R.TemplateNameTemplateVehicles. diff --git a/internal/infrastructure/db/models/test_signals.go b/internal/infrastructure/db/models/test_signals.go index d7dbaf65..d211c1f5 100644 --- a/internal/infrastructure/db/models/test_signals.go +++ b/internal/infrastructure/db/models/test_signals.go @@ -1,4 +1,4 @@ -// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// Code generated by SQLBoiler 4.15.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models