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
|
- `dao` field to store DAO (Datum As Offset) extension data for extra position precision
|
||||||
- Automatic detection of items/objects during packet processing
|
- Automatic detection of items/objects during packet processing
|
||||||
- Database indexes for efficient item/object queries
|
- 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
|
||||||
- Fixed packet storage failure in production due to non-schema fields from updated APRS parser
|
- 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
|
- 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
|
- 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
|
- 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
|
### Added
|
||||||
- Database query optimization with 10 new performance indexes
|
- Database query optimization with 10 new performance indexes
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,9 @@ defmodule Aprsme.PacketConsumer do
|
||||||
# Detect and set item/object fields
|
# Detect and set item/object fields
|
||||||
attrs = detect_item_or_object(attrs)
|
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
|
# Normalize data_type to string if it's an atom
|
||||||
attrs = normalize_data_type(attrs)
|
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