fix: resolve VARCHAR(255) database errors by migrating to TEXT fields

- Create migration to change string fields from VARCHAR(255) to TEXT type
- Add PacketSanitizer module for UTF-8 safe string truncation
- Update PacketConsumer to sanitize data before database insertion
- Prevents database errors from overly long packet data fields
This commit is contained in:
Graham McIntire 2025-08-09 16:54:38 -04:00
parent a636410171
commit 706721b06f
No known key found for this signature in database
4 changed files with 146 additions and 0 deletions

View file

@ -14,12 +14,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `dao` field to store DAO (Datum As Offset) extension data for extra position precision
- Automatic detection of items/objects during packet processing
- Database indexes for efficient item/object queries
- Packet data sanitization to prevent database field overflow errors
- Created `Aprsme.PacketSanitizer` module to truncate overly long strings
- Implemented UTF-8 safe string truncation
### Fixed
- Fixed packet storage failure in production due to non-schema fields from updated APRS parser
- Remove `type`, `digipeaters`, `daodatumbyte`, `mbits`, `message`, `phg`, `wx`, `resultcode`, `resultmsg` fields before database insertion
- Enhanced error logging to capture sample packet data when batch inserts fail
- Created helper function `remove_non_schema_fields/1` to centralize field removal logic
- Fixed VARCHAR(255) database errors by migrating string fields to TEXT type
- Migration increases field sizes for: comment, message_text, raw_packet, body, origpacket, header, radiorange, item_name, object_name, telemetry_bits, device_identifier, format
- Added sanitization in PacketConsumer to truncate strings before insertion as safety measure
### Added
- Database query optimization with 10 new performance indexes

View file

@ -306,6 +306,9 @@ defmodule Aprsme.PacketConsumer do
# Detect and set item/object fields
attrs = detect_item_or_object(attrs)
# Sanitize packet data to prevent database field overflow
attrs = Aprsme.PacketSanitizer.sanitize_packet(attrs)
# Normalize data_type to string if it's an atom
attrs = normalize_data_type(attrs)

View file

@ -0,0 +1,106 @@
defmodule Aprsme.PacketSanitizer do
@moduledoc """
Sanitizes packet data to ensure it fits within database constraints.
This module provides truncation for long strings to prevent database errors.
"""
# Define max lengths for fields that might still have constraints
# Even though we're migrating to text, this provides safety
@max_lengths %{
# Keep reasonable limits for key fields
base_callsign: 20,
sender: 20,
destination: 20,
ssid: 10,
data_type: 50,
symbol_code: 5,
symbol_table_id: 5,
region: 50,
timestamp: 50,
message_number: 20,
addressee: 50,
symboltable: 5,
symbolcode: 5,
srccallsign: 20,
dstcallsign: 20,
# These fields can be longer but still have sanity limits
path: 500,
manufacturer: 100,
equipment_type: 100,
format: 100,
device_identifier: 255,
item_name: 100,
object_name: 100,
# Very long fields - these will be TEXT in DB but we still
# want to prevent abuse with extremely long data
information_field: 5000,
raw_packet: 5000,
comment: 2000,
message_text: 2000,
body: 5000,
origpacket: 5000,
header: 1000,
radiorange: 1000,
telemetry_bits: 1000
}
@doc """
Sanitizes a packet map by truncating string fields that exceed maximum lengths.
"""
@spec sanitize_packet(map()) :: map()
def sanitize_packet(packet) when is_map(packet) do
Enum.reduce(packet, %{}, fn {key, value}, acc ->
sanitized_value = sanitize_field(key, value)
Map.put(acc, key, sanitized_value)
end)
end
defp sanitize_field(key, value) when is_binary(value) do
case Map.get(@max_lengths, key) do
nil ->
# No limit defined, return as-is
value
max_length ->
truncate_string(value, max_length)
end
end
defp sanitize_field(_key, value), do: value
defp truncate_string(string, max_length) when byte_size(string) <= max_length do
string
end
defp truncate_string(string, max_length) do
# Use binary_part to safely truncate at byte boundaries
# This prevents splitting UTF-8 characters
truncated = binary_part(string, 0, max_length)
# Ensure we don't end in the middle of a UTF-8 character
if String.valid?(truncated) do
truncated
else
truncate_to_valid_utf8(string, max_length)
end
end
defp truncate_to_valid_utf8(string, max_length) do
# Work backwards from max_length to find a valid UTF-8 boundary
truncate_to_valid_utf8(string, max_length - 1, max_length)
end
defp truncate_to_valid_utf8(_string, 0, _original_max), do: ""
defp truncate_to_valid_utf8(string, current_length, original_max) do
truncated = binary_part(string, 0, current_length)
if String.valid?(truncated) do
truncated
else
truncate_to_valid_utf8(string, current_length - 1, original_max)
end
end
end

View file

@ -0,0 +1,31 @@
defmodule Aprsme.Repo.Migrations.IncreaseAllStringFieldsToText do
use Ecto.Migration
def change do
alter table(:packets) do
# Fields that commonly have longer content
modify :comment, :text, from: :string
modify :message_text, :text, from: :string
modify :raw_packet, :text, from: :string
modify :body, :text, from: :string
modify :origpacket, :text, from: :string
modify :header, :text, from: :string
# Radio range can be long
modify :radiorange, :text, from: :string
# Object/item names can potentially be long
modify :item_name, :text, from: :string
modify :object_name, :text, from: :string
# Telemetry bits field
modify :telemetry_bits, :text, from: :string
# Device identifier could be long
modify :device_identifier, :text, from: :string
# Format field might have long descriptions
modify :format, :text, from: :string
end
end
end