Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
DO
$$
DECLARE
playing_row RECORD;
midi_event JSONB;
event_ordinality BIGINT;
timestamp_value JSONB;
numeric_value NUMERIC;
BEGIN
FOR playing_row IN
SELECT playing_id, midi_data
FROM playing
WHERE midi_data IS NOT NULL
LOOP
IF jsonb_typeof(playing_row.midi_data) IS DISTINCT FROM 'array' THEN
RAISE EXCEPTION 'playing %.midi_data must be a JSON array', playing_row.playing_id;
END IF;

FOR midi_event, event_ordinality IN
SELECT value, ordinality
FROM jsonb_array_elements(playing_row.midi_data) WITH ORDINALITY
LOOP
IF jsonb_typeof(midi_event) IS DISTINCT FROM 'object' THEN
RAISE EXCEPTION 'playing %.midi_data[%] must be a JSON object',
playing_row.playing_id, event_ordinality - 1;
END IF;

IF jsonb_typeof(midi_event -> 'type') IS DISTINCT FROM 'string'
OR midi_event ->> 'type' NOT IN ('NOTE_ON', 'NOTE_OFF') THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid type',
playing_row.playing_id, event_ordinality - 1;
END IF;

IF jsonb_typeof(midi_event -> 'pitch') IS DISTINCT FROM 'number' THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid pitch',
playing_row.playing_id, event_ordinality - 1;
END IF;
numeric_value := (midi_event ->> 'pitch')::NUMERIC;
IF numeric_value <> trunc(numeric_value) OR numeric_value < 0 OR numeric_value > 127 THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid pitch',
playing_row.playing_id, event_ordinality - 1;
END IF;

IF jsonb_typeof(midi_event -> 'velocity') IS DISTINCT FROM 'number' THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid velocity',
playing_row.playing_id, event_ordinality - 1;
END IF;
numeric_value := (midi_event ->> 'velocity')::NUMERIC;
IF numeric_value <> trunc(numeric_value) OR numeric_value < 0 OR numeric_value > 127 THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid velocity',
playing_row.playing_id, event_ordinality - 1;
END IF;

timestamp_value := COALESCE(
NULLIF(midi_event -> 'timestamp_ms', 'null'::JSONB),
NULLIF(midi_event -> 'timestampMs', 'null'::JSONB)
);
IF jsonb_typeof(timestamp_value) IS DISTINCT FROM 'number' THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid timestamp',
playing_row.playing_id, event_ordinality - 1;
END IF;
numeric_value := (timestamp_value #>> '{}')::NUMERIC;
IF numeric_value <> trunc(numeric_value) OR numeric_value < 0 OR numeric_value > 9223372036854775807 THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid timestamp',
playing_row.playing_id, event_ordinality - 1;
END IF;

IF NULLIF(midi_event -> 'sequence', 'null'::JSONB) IS NOT NULL THEN
IF jsonb_typeof(midi_event -> 'sequence') IS DISTINCT FROM 'number' THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid sequence',
playing_row.playing_id, event_ordinality - 1;
END IF;
numeric_value := (midi_event ->> 'sequence')::NUMERIC;
IF numeric_value <> trunc(numeric_value) OR numeric_value < 0 OR numeric_value > 2147483647 THEN
RAISE EXCEPTION 'playing %.midi_data[%] has an invalid sequence',
playing_row.playing_id, event_ordinality - 1;
END IF;
END IF;
END LOOP;
END LOOP;
END
$$;

UPDATE playing AS p
SET midi_data = CASE
WHEN p.midi_data IS NULL THEN '[]'::JSONB
ELSE COALESCE(
(
SELECT jsonb_agg(
(midi_event - 'timestampMs' - 'sequence')
|| jsonb_build_object(
'sequence', COALESCE(
NULLIF(midi_event -> 'sequence', 'null'::JSONB),
to_jsonb((event_ordinality - 1)::INTEGER)
),
'timestamp_ms', COALESCE(
NULLIF(midi_event -> 'timestamp_ms', 'null'::JSONB),
NULLIF(midi_event -> 'timestampMs', 'null'::JSONB)
)
)
ORDER BY event_ordinality
Comment on lines +89 to +101

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

sequence 기본값의 의미를 재확인하세요.

MidiEventData.sequence의 주석은 "동일 timestamp 내 순서 값"입니다. 여기서는 배열 전체의 0-based 인덱스를 넣습니다. 값은 sequence >= 0 검증을 통과하지만, 의미는 원래 정의와 다릅니다. 이후 동일 timestamp 그룹을 sequence로 정렬하거나 그룹 크기를 계산하는 로직이 추가되면 잘못된 결과가 나옵니다.

동일 timestamp 내 순번으로 채우려면 timestamp 값으로 파티션한 윈도 함수를 사용하세요.

♻️ timestamp 기준 순번을 부여하는 예시
             SELECT jsonb_agg(
                 (midi_event - 'timestampMs' - 'sequence')
                     || jsonb_build_object(
                         'sequence', COALESCE(
                             NULLIF(midi_event -> 'sequence', 'null'::JSONB),
-                            to_jsonb((event_ordinality - 1)::INTEGER)
+                            to_jsonb((row_number() OVER (
+                                PARTITION BY COALESCE(
+                                    NULLIF(midi_event -> 'timestamp_ms', 'null'::JSONB),
+                                    NULLIF(midi_event -> 'timestampMs', 'null'::JSONB)
+                                )
+                                ORDER BY event_ordinality
+                            ) - 1)::INTEGER)
                         ),

참고: PostgreSQL 윈도 함수 문서(row_number, PARTITION BY)를 확인하세요. 윈도 함수와 jsonb_agg 집계를 같은 SELECT 절에 두면 계산 순서 문제가 생기므로, 서브쿼리로 순번을 먼저 계산한 뒤 집계하는 구조를 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/db/migration/V3__normalize_legacy_playing_midi_data.sql`
around lines 89 - 101, Update the sequence fallback in the JSON aggregation to
represent the 0-based order within each identical timestamp group, rather than
the array-wide event_ordinality. Compute the timestamp-partitioned row number in
a subquery before jsonb_agg, while preserving existing sequence values and
timestamp_ms fallback behavior.

)
FROM jsonb_array_elements(p.midi_data) WITH ORDINALITY
AS events(midi_event, event_ordinality)
),
'[]'::JSONB
)
END
WHERE p.midi_data IS NULL
OR EXISTS (
SELECT 1
FROM jsonb_array_elements(p.midi_data) AS events(midi_event)
WHERE NULLIF(midi_event -> 'sequence', 'null'::JSONB) IS NULL
OR NULLIF(midi_event -> 'timestamp_ms', 'null'::JSONB) IS NULL
OR midi_event ? 'timestampMs'
);
Loading