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:
parent
a636410171
commit
706721b06f
4 changed files with 146 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
106
lib/aprsme/packet_sanitizer.ex
Normal file
106
lib/aprsme/packet_sanitizer.ex
Normal 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
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue