99import java .io .UnsupportedEncodingException ;
1010import java .net .URLEncoder ;
1111import java .nio .charset .StandardCharsets ;
12+ import java .util .concurrent .ConcurrentHashMap ;
1213import org .slf4j .Logger ;
1314import org .slf4j .LoggerFactory ;
1415
@@ -32,17 +33,63 @@ public class SharedDBCommenter {
3233 private static final String TRACEPARENT = encode ("traceparent" );
3334 private static final String DD_SERVICE_HASH = encode ("ddsh" );
3435
36+ /**
37+ * Cache for static comment strings (those without traceParent or peerService). The key combines
38+ * dbService, hostname, dbName, and Config identity to ensure correctness if Config is replaced.
39+ */
40+ private static final ConcurrentHashMap <StaticCommentKey , String > staticCommentCache =
41+ new ConcurrentHashMap <>();
42+
43+ // Pre-computed marker strings for trace comment detection
44+ private static final String PARENT_SERVICE_EQ = PARENT_SERVICE + "=" ;
45+ private static final String DATABASE_SERVICE_EQ = DATABASE_SERVICE + "=" ;
46+ private static final String DD_HOSTNAME_EQ = DD_HOSTNAME + "=" ;
47+ private static final String DD_DB_NAME_EQ = DD_DB_NAME + "=" ;
48+ private static final String DD_PEER_SERVICE_EQ = DD_PEER_SERVICE + "=" ;
49+ private static final String DD_ENV_EQ = DD_ENV + "=" ;
50+ private static final String DD_VERSION_EQ = DD_VERSION + "=" ;
51+ private static final String TRACEPARENT_EQ = TRACEPARENT + "=" ;
52+ private static final String DD_SERVICE_HASH_EQ = DD_SERVICE_HASH + "=" ;
53+
3554 // Used by SQLCommenter and MongoCommentInjector to avoid duplicate comment injection
3655 public static boolean containsTraceComment (String commentContent ) {
37- return commentContent .contains (PARENT_SERVICE + "=" )
38- || commentContent .contains (DATABASE_SERVICE + "=" )
39- || commentContent .contains (DD_HOSTNAME + "=" )
40- || commentContent .contains (DD_DB_NAME + "=" )
41- || commentContent .contains (DD_PEER_SERVICE + "=" )
42- || commentContent .contains (DD_ENV + "=" )
43- || commentContent .contains (DD_VERSION + "=" )
44- || commentContent .contains (TRACEPARENT + "=" )
45- || commentContent .contains (DD_SERVICE_HASH + "=" );
56+ return commentContent .contains (PARENT_SERVICE_EQ )
57+ || commentContent .contains (DATABASE_SERVICE_EQ )
58+ || commentContent .contains (DD_HOSTNAME_EQ )
59+ || commentContent .contains (DD_DB_NAME_EQ )
60+ || commentContent .contains (DD_PEER_SERVICE_EQ )
61+ || commentContent .contains (DD_ENV_EQ )
62+ || commentContent .contains (DD_VERSION_EQ )
63+ || commentContent .contains (TRACEPARENT_EQ )
64+ || commentContent .contains (DD_SERVICE_HASH_EQ );
65+ }
66+
67+ /**
68+ * Checks for trace comment markers within a range of the given string, without allocating a
69+ * substring. Searches within [fromIndex, toIndex) of the source string.
70+ */
71+ public static boolean containsTraceComment (String sql , int fromIndex , int toIndex ) {
72+ return containsInRange (sql , PARENT_SERVICE_EQ , fromIndex , toIndex )
73+ || containsInRange (sql , DATABASE_SERVICE_EQ , fromIndex , toIndex )
74+ || containsInRange (sql , DD_HOSTNAME_EQ , fromIndex , toIndex )
75+ || containsInRange (sql , DD_DB_NAME_EQ , fromIndex , toIndex )
76+ || containsInRange (sql , DD_PEER_SERVICE_EQ , fromIndex , toIndex )
77+ || containsInRange (sql , DD_ENV_EQ , fromIndex , toIndex )
78+ || containsInRange (sql , DD_VERSION_EQ , fromIndex , toIndex )
79+ || containsInRange (sql , TRACEPARENT_EQ , fromIndex , toIndex )
80+ || containsInRange (sql , DD_SERVICE_HASH_EQ , fromIndex , toIndex );
81+ }
82+
83+ /** Checks if {@code target} appears within the range [fromIndex, toIndex) of {@code source}. */
84+ private static boolean containsInRange (String source , String target , int fromIndex , int toIndex ) {
85+ int targetLen = target .length ();
86+ int limit = toIndex - targetLen ;
87+ for (int i = fromIndex ; i <= limit ; i ++) {
88+ if (source .regionMatches (i , target , 0 , targetLen )) {
89+ return true ;
90+ }
91+ }
92+ return false ;
4693 }
4794
4895 // Build database comment content without comment delimiters such as /* */
@@ -69,6 +116,60 @@ public static String buildComment(
69116 return sb .length () > 0 ? sb .toString () : null ;
70117 }
71118
119+ /**
120+ * Builds the static portion of a database comment that does not change per-span. This includes
121+ * parentService, databaseService, hostname, dbName, env, version, and serviceHash. The dynamic
122+ * parts (peerService, traceParent) are excluded and must be appended separately.
123+ *
124+ * <p>Results are cached per (dbService, hostname, dbName, Config) combination to avoid redundant
125+ * URLEncoder.encode() calls and StringBuilder work on every query execution.
126+ *
127+ * @return the static comment prefix, or null if no static fields are set
128+ */
129+ public static String buildStaticComment (String dbService , String hostname , String dbName ) {
130+ Config config = Config .get ();
131+ StaticCommentKey key = new StaticCommentKey (dbService , hostname , dbName , config );
132+ String cached = staticCommentCache .get (key );
133+ if (cached != null ) {
134+ return cached ;
135+ }
136+
137+ StringBuilder sb = new StringBuilder ();
138+
139+ int initSize = 0 ;
140+ append (sb , PARENT_SERVICE , config .getServiceName (), initSize );
141+ append (sb , DATABASE_SERVICE , dbService , initSize );
142+ append (sb , DD_HOSTNAME , hostname , initSize );
143+ append (sb , DD_DB_NAME , dbName , initSize );
144+ // peerService is per-span, skip here
145+ append (sb , DD_ENV , config .getEnv (), initSize );
146+ append (sb , DD_VERSION , config .getVersion (), initSize );
147+ // traceParent is per-span, skip here
148+
149+ if (config .isDbmInjectSqlBaseHash () && config .isExperimentalPropagateProcessTagsEnabled ()) {
150+ append (sb , DD_SERVICE_HASH , BaseHash .getBaseHashStr (), initSize );
151+ }
152+
153+ String result = sb .length () > 0 ? sb .toString () : null ;
154+ if (result != null ) {
155+ staticCommentCache .putIfAbsent (key , result );
156+ }
157+ return result ;
158+ }
159+
160+ /** Returns true if the current active span has a non-null, non-empty peer service tag. */
161+ public static boolean hasPeerService () {
162+ AgentSpan span = activeSpan ();
163+ if (span != null ) {
164+ Object peerService = span .getTag (Tags .PEER_SERVICE );
165+ if (peerService != null ) {
166+ String str = peerService .toString ();
167+ return str != null && !str .isEmpty ();
168+ }
169+ }
170+ return false ;
171+ }
172+
72173 private static String getPeerService () {
73174 AgentSpan span = activeSpan ();
74175 Object peerService = null ;
@@ -104,4 +205,50 @@ private static void append(StringBuilder sb, String key, String value, int initS
104205 }
105206 sb .append (key ).append (EQUALS ).append (QUOTE ).append (encodedValue ).append (QUOTE );
106207 }
208+
209+ /**
210+ * Cache key for static comment lookup. Uses Config identity (rather than individual field values)
211+ * to ensure the cache is automatically invalidated if Config is replaced (as happens in tests).
212+ * In production, Config is created once, so identity comparison is both correct and cheap.
213+ */
214+ private static final class StaticCommentKey {
215+ private final String dbService ;
216+ private final String hostname ;
217+ private final String dbName ;
218+ private final Config config ;
219+ private final int hashCode ;
220+
221+ StaticCommentKey (String dbService , String hostname , String dbName , Config config ) {
222+ this .dbService = dbService ;
223+ this .hostname = hostname ;
224+ this .dbName = dbName ;
225+ this .config = config ;
226+ int h = 17 ;
227+ h = 31 * h + (dbService != null ? dbService .hashCode () : 0 );
228+ h = 31 * h + (hostname != null ? hostname .hashCode () : 0 );
229+ h = 31 * h + (dbName != null ? dbName .hashCode () : 0 );
230+ h = 31 * h + System .identityHashCode (config );
231+ this .hashCode = h ;
232+ }
233+
234+ @ Override
235+ public boolean equals (Object o ) {
236+ if (this == o ) return true ;
237+ if (!(o instanceof StaticCommentKey )) return false ;
238+ StaticCommentKey that = (StaticCommentKey ) o ;
239+ return config == that .config
240+ && eq (dbService , that .dbService )
241+ && eq (hostname , that .hostname )
242+ && eq (dbName , that .dbName );
243+ }
244+
245+ private static boolean eq (String a , String b ) {
246+ return a == null ? b == null : a .equals (b );
247+ }
248+
249+ @ Override
250+ public int hashCode () {
251+ return hashCode ;
252+ }
253+ }
107254}
0 commit comments