150 lines
5.1 KiB
Go
150 lines
5.1 KiB
Go
package stats
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const HydrogenTankCapacityTableSQL = `CREATE TABLE IF NOT EXISTS vehicle_hydrogen_tank_capacity (
|
|
vin VARCHAR(64) NOT NULL,
|
|
source_vehicle_id BIGINT NOT NULL,
|
|
source_model_id BIGINT NULL,
|
|
plate_number VARCHAR(50) NOT NULL DEFAULT '',
|
|
brand_name VARCHAR(500) NOT NULL DEFAULT '',
|
|
model_name VARCHAR(500) NOT NULL DEFAULT '',
|
|
tank_capacity_l DECIMAL(12,2) NOT NULL,
|
|
active TINYINT(1) NOT NULL DEFAULT 1,
|
|
source_updated_at DATETIME NULL,
|
|
synced_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (vin),
|
|
KEY idx_hydrogen_capacity_model (source_model_id),
|
|
KEY idx_hydrogen_capacity_active (active,vin)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
type HydrogenCapacitySyncResult struct {
|
|
Read int
|
|
Written int
|
|
Deactivated int64
|
|
}
|
|
|
|
type hydrogenCapacityRow struct {
|
|
VIN string
|
|
SourceVehicleID int64
|
|
SourceModelID sql.NullInt64
|
|
Plate string
|
|
Brand string
|
|
Model string
|
|
CapacityLiters float64
|
|
SourceUpdatedAt sql.NullTime
|
|
}
|
|
|
|
var mysqlIdentifier = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
|
|
|
|
func SyncHydrogenTankCapacities(ctx context.Context, db *sql.DB, sourceSchema string) (HydrogenCapacitySyncResult, error) {
|
|
result := HydrogenCapacitySyncResult{}
|
|
if db == nil {
|
|
return result, fmt.Errorf("mysql database is required")
|
|
}
|
|
sourceSchema = strings.TrimSpace(sourceSchema)
|
|
if !mysqlIdentifier.MatchString(sourceSchema) {
|
|
return result, fmt.Errorf("invalid asset schema %q", sourceSchema)
|
|
}
|
|
query := fmt.Sprintf(`SELECT vi.id,vi.vehicle_model_id,UPPER(TRIM(vi.vin)),COALESCE(vi.plate_number,''),
|
|
COALESCE(vm.brand,''),COALESCE(vm.model,''),vm.tank_capacity,
|
|
CASE
|
|
WHEN vi.update_time IS NULL THEN vm.update_time
|
|
WHEN vm.update_time IS NULL THEN vi.update_time
|
|
ELSE GREATEST(vi.update_time,vm.update_time)
|
|
END
|
|
FROM %s.vehicle_info vi
|
|
JOIN %s.vehicle_model vm ON vm.id=vi.vehicle_model_id
|
|
WHERE COALESCE(vi.del_flag,'0')='0' AND COALESCE(vm.del_flag,'0')='0'
|
|
AND LENGTH(TRIM(vi.vin))=17 AND vm.tank_capacity>0`, sourceSchema, sourceSchema)
|
|
rows, err := db.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return result, fmt.Errorf("read asset hydrogen capacities: %w", err)
|
|
}
|
|
byVIN := map[string]hydrogenCapacityRow{}
|
|
for rows.Next() {
|
|
var row hydrogenCapacityRow
|
|
if err := rows.Scan(&row.SourceVehicleID, &row.SourceModelID, &row.VIN, &row.Plate, &row.Brand, &row.Model, &row.CapacityLiters, &row.SourceUpdatedAt); err != nil {
|
|
rows.Close()
|
|
return result, err
|
|
}
|
|
result.Read++
|
|
if row.CapacityLiters <= 0 || row.CapacityLiters > 10000 {
|
|
continue
|
|
}
|
|
if previous, exists := byVIN[row.VIN]; !exists || newerCapacityRow(row, previous) {
|
|
byVIN[row.VIN] = row
|
|
}
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return result, err
|
|
}
|
|
tx, err := db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
defer tx.Rollback()
|
|
_, err = tx.ExecContext(ctx, `UPDATE vehicle_hydrogen_tank_capacity SET active=0 WHERE active=1`)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
const upsert = `INSERT INTO vehicle_hydrogen_tank_capacity(
|
|
vin,source_vehicle_id,source_model_id,plate_number,brand_name,model_name,tank_capacity_l,active,source_updated_at,synced_at
|
|
) VALUES(?,?,?,?,?,?,?,1,?,NOW(3))
|
|
ON DUPLICATE KEY UPDATE source_vehicle_id=VALUES(source_vehicle_id),source_model_id=VALUES(source_model_id),
|
|
plate_number=VALUES(plate_number),brand_name=VALUES(brand_name),model_name=VALUES(model_name),
|
|
tank_capacity_l=VALUES(tank_capacity_l),active=1,source_updated_at=VALUES(source_updated_at),synced_at=NOW(3)`
|
|
for _, row := range byVIN {
|
|
if _, err := tx.ExecContext(ctx, upsert, row.VIN, row.SourceVehicleID, row.SourceModelID, row.Plate, row.Brand, row.Model, row.CapacityLiters, row.SourceUpdatedAt); err != nil {
|
|
return result, err
|
|
}
|
|
result.Written++
|
|
}
|
|
if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM vehicle_hydrogen_tank_capacity WHERE active=0`).Scan(&result.Deactivated); err != nil {
|
|
return result, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return result, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func newerCapacityRow(left, right hydrogenCapacityRow) bool {
|
|
if left.SourceUpdatedAt.Valid != right.SourceUpdatedAt.Valid {
|
|
return left.SourceUpdatedAt.Valid
|
|
}
|
|
if left.SourceUpdatedAt.Valid && !left.SourceUpdatedAt.Time.Equal(right.SourceUpdatedAt.Time) {
|
|
return left.SourceUpdatedAt.Time.After(right.SourceUpdatedAt.Time)
|
|
}
|
|
return left.SourceVehicleID > right.SourceVehicleID
|
|
}
|
|
|
|
func LoadHydrogenTankCapacities(ctx context.Context, query Queryer) (map[string]float64, error) {
|
|
if query == nil {
|
|
return nil, fmt.Errorf("mysql queryer is required")
|
|
}
|
|
rows, err := query.QueryContext(ctx, `SELECT UPPER(TRIM(vin)),tank_capacity_l FROM vehicle_hydrogen_tank_capacity WHERE active=1 AND tank_capacity_l>0`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
capacities := map[string]float64{}
|
|
for rows.Next() {
|
|
var vin string
|
|
var capacity float64
|
|
if err := rows.Scan(&vin, &capacity); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(vin) == 17 && capacity > 0 && capacity <= 10000 {
|
|
capacities[vin] = capacity
|
|
}
|
|
}
|
|
return capacities, rows.Err()
|
|
}
|