@@ -675,14 +675,13 @@ create or replace package body ut_utils is
675675 l_eq_end_char varchar2(1 char);
676676 l_pos binary_integer;
677677 l_end binary_integer;
678- l_token_count binary_integer;
679- l_has_ml_comment boolean := false;
678+ l_has_ml_comment boolean := false;
680679 begin
681680 if a_source.count = 0 then
682681 return a_source;
683682 end if;
684683
685- -- Fast pre-scan: check if any /* exists at all if not, nothing to do — return source as-is
684+ -- Fast pre-scan to check for presence of multi-line comments; if none, return original source unmodified
686685 for i in 1 .. a_source.count loop
687686 if instr(a_source(i), '/*') > 0 then
688687 l_has_ml_comment := true;
@@ -697,20 +696,20 @@ create or replace package body ut_utils is
697696 for i in 1 .. a_source.count loop
698697 l_line := a_source(i);
699698
700- -- Fast path: currently inside a multi-line comment, look only for closing */
699+ -- Fast path: inside multi-line comment
701700 if l_in_ml_comment then
702701 l_ml_end := instr(l_line, '*/');
703702 if l_ml_end > 0 then
704703 l_in_ml_comment := false;
705704 l_line := substr(l_line, l_ml_end + 2);
706- -- fall through to normal scan of remainder of line, in case there are more /* comments on the same line
705+ -- fall through to normal scan
707706 else
708707 l_result(i) := '';
709708 continue;
710709 end if;
711710 end if;
712711
713- -- Fast path: no special tokens on this line at all, just copy it to result and move on
712+ -- Fast path: no special tokens on this line
714713 if instr(l_line, '/') = 0
715714 and instr(l_line, '-') = 0
716715 and instr(l_line, '''') = 0
@@ -719,7 +718,7 @@ create or replace package body ut_utils is
719718 continue;
720719 end if;
721720
722- -- Normal scan: consume one token at a time, advance l_remaining until end of line
721+ -- Normal scan
723722 l_remaining := l_line;
724723 l_line := null;
725724
@@ -729,83 +728,71 @@ create or replace package body ut_utils is
729728 l_ml_start := instr(l_remaining, '/*');
730729 l_comment_start := instr(l_remaining, '--');
731730 l_text_start := instr(l_remaining, '''');
732- -- only search for q' if ' was found — q' always contains ' and would be misidentified otherwise
733- l_eq_text_start := case when l_text_start > 0 then instr(l_remaining, 'q''') else 0 end;
734- -- count how many tokens are present to decide if we can skip LEAST/GREATEST and just use the one that is present
735- l_token_count := sign(l_ml_start) + sign(l_comment_start)
736- + sign(l_text_start) + sign(l_eq_text_start);
737-
738- -- no special tokens left — consume remainder and stop scanning this line
739- if l_token_count = 0 then
731+ -- q' always puts ' at l_text_start; just check the char immediately before it
732+ l_eq_text_start := case
733+ when l_text_start > 1 and substr(l_remaining, l_text_start - 1, 1) = 'q'
734+ then l_text_start - 1
735+ else 0
736+ end;
737+ -- Sentinel gc_max_plsql_source_len means "not present"; 32767 is beyond any VARCHAR2 position
738+ l_pos := least(
739+ case when l_ml_start > 0 then l_ml_start else gc_max_plsql_source_len end,
740+ case when l_comment_start > 0 then l_comment_start else gc_max_plsql_source_len end,
741+ case when l_text_start > 0 then l_text_start else gc_max_plsql_source_len end,
742+ case when l_eq_text_start > 0 then l_eq_text_start else gc_max_plsql_source_len end
743+ );
744+
745+ if l_pos = gc_max_plsql_source_len then
740746 l_line := l_line || l_remaining;
741747 exit scan_line;
742748 end if;
743749
744- -- only one token present — skip LEAST, use GREATEST to find it since 0 means not present and any positive value is a valid position
745- if l_token_count = 1 then
746- l_pos := greatest(l_ml_start, l_comment_start, l_text_start, l_eq_text_start);
747- else
748- l_pos := least(
749- case when l_ml_start > 0 then l_ml_start else gc_max_plsql_source_len end,
750- case when l_comment_start > 0 then l_comment_start else gc_max_plsql_source_len end,
751- case when l_text_start > 0 then l_text_start else gc_max_plsql_source_len end,
752- case when l_eq_text_start > 0 then l_eq_text_start else gc_max_plsql_source_len end
753- );
754- end if;
755-
756- -- q-quoted string: checked before plain quote because q' contains ' and would be misidentified
757- if l_pos = l_eq_text_start
758- and (l_ml_start = 0 or l_eq_text_start < l_ml_start)
759- and (l_comment_start = 0 or l_eq_text_start < l_comment_start)
760- and (l_text_start = 0 or l_eq_text_start < l_text_start)
761- then
762- l_eq_end_char := translate(substr(l_remaining, l_eq_text_start + 2, 1),gc_open_chars,gc_close_chars);
763- l_end := instr(l_remaining, l_eq_end_char || '''', l_eq_text_start + 3);
750+ l_line := l_line || substr(l_remaining, 1, l_pos - 1);
751+ l_remaining := substr(l_remaining, l_pos);
752+ -- l_remaining now starts exactly at the token; all branch offsets below are relative to 1
753+ if l_pos = l_eq_text_start then
754+ -- q-quoted string: l_remaining starts at 'q', delimiter is at position 3
755+ l_eq_end_char := translate(substr(l_remaining, 3, 1), gc_open_chars, gc_close_chars);
756+ l_end := instr(l_remaining, l_eq_end_char || '''', 4);
764757 if l_end > 0 then
765- l_line := l_line || substr(l_remaining, 1, l_end + 1);
758+ l_line := l_line || substr(l_remaining, 1, l_end + 1);
766759 l_remaining := substr(l_remaining, l_end + 2);
767760 else
768761 l_line := l_line || l_remaining;
769762 exit scan_line;
770763 end if;
771- -- Multi-line comment start: skip it, look for end of comment, continue scanning line after it
772- elsif l_pos = l_ml_start
773- and (l_comment_start = 0 or l_ml_start < l_comment_start)
774- and (l_text_start = 0 or l_ml_start < l_text_start)
775- and (l_eq_text_start = 0 or l_ml_start < l_eq_text_start)
776- then
777- l_line := l_line || substr(l_remaining, 1, l_ml_start - 1);
778- l_ml_end := instr(l_remaining, '*/', l_ml_start + 2);
764+
765+ elsif l_pos = l_ml_start then
766+ -- Multi-line comment: l_remaining starts at '/*', so end search starts at 3
767+ l_ml_end := instr(l_remaining, '*/', 3);
779768 if l_ml_end > 0 then
780769 l_remaining := substr(l_remaining, l_ml_end + 2);
781770 else
782771 l_in_ml_comment := true;
783772 exit scan_line;
784773 end if;
785- -- Single-line comment: keep it, stop scanning this line since everything after -- is comment anyway
786- elsif l_pos = l_comment_start
787- and (l_ml_start = 0 or l_comment_start < l_ml_start)
788- and (l_text_start = 0 or l_comment_start < l_text_start)
789- and (l_eq_text_start = 0 or l_comment_start < l_eq_text_start)
790- then
774+
775+ elsif l_pos = l_comment_start then
776+ -- Single-line comment: everything from here is comment, keep and stop
791777 l_line := l_line || l_remaining;
792778 exit scan_line;
793- -- Regular string literal start: keep it, scan forward for closing quote, handle '' escaped quotes properly by skipping them and keep scanning
779+
794780 else
795- -- scan forward continuously to handle '' escaped quotes
796- l_end := l_text_start + 1;
781+ -- Regular string literal: l_remaining starts at the opening quote
782+ -- scan from position 2 to skip the opening quote
783+ l_end := 2;
797784 loop
798785 l_end := instr(l_remaining, '''', l_end);
799786 exit when l_end = 0;
800787 if substr(l_remaining, l_end, 2) = '''''' then
801- l_end := l_end + 2; -- skip escaped quote, keep scanning
788+ l_end := l_end + 2; -- skip escaped quote pair
802789 else
803- exit; -- found real closing quote
790+ exit; -- real closing quote
804791 end if;
805792 end loop;
806793
807794 if l_end > 0 then
808- l_line := l_line || substr(l_remaining, 1, l_end);
795+ l_line := l_line || substr(l_remaining, 1, l_end);
809796 l_remaining := substr(l_remaining, l_end + 1);
810797 else
811798 l_line := l_line || l_remaining;
@@ -814,8 +801,10 @@ create or replace package body ut_utils is
814801
815802 end if;
816803 end loop scan_line;
804+
817805 l_result(i) := l_line;
818806 end loop;
807+
819808 return l_result;
820809 end replace_multiline_comments;
821810
0 commit comments