001/*-
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.util;
021
022import ca.uhn.fhir.interceptor.model.RequestPartitionId;
023import ca.uhn.fhir.util.UrlUtil;
024import org.apache.commons.lang3.Validate;
025import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.List;
032
033import static org.apache.commons.lang3.StringUtils.trim;
034
035public class SqlQuery {
036        private static final Logger ourLog = LoggerFactory.getLogger(SqlQuery.class);
037        private final String myThreadName = Thread.currentThread().getName();
038        private final String mySql;
039        private final List<String> myParams;
040        private final long myQueryTimestamp;
041        private final long myElapsedTime;
042        private final StackTraceElement[] myStackTrace;
043        private final int mySize;
044        private final LanguageEnum myLanguage;
045        private final String myNamespace;
046        private final RequestPartitionId myRequestPartitionId;
047
048        public SqlQuery(
049                        String theSql,
050                        List<String> theParams,
051                        long theQueryTimestamp,
052                        long theElapsedTime,
053                        StackTraceElement[] theStackTraceElements,
054                        int theSize,
055                        RequestPartitionId theRequestPartitionId) {
056                this(
057                                null,
058                                theSql,
059                                theParams,
060                                theQueryTimestamp,
061                                theElapsedTime,
062                                theStackTraceElements,
063                                theSize,
064                                LanguageEnum.SQL,
065                                theRequestPartitionId);
066        }
067
068        public SqlQuery(
069                        String theNamespace,
070                        String theSql,
071                        List<String> theParams,
072                        long theQueryTimestamp,
073                        long theElapsedTime,
074                        StackTraceElement[] theStackTraceElements,
075                        int theSize,
076                        LanguageEnum theLanguage,
077                        RequestPartitionId theRequestPartitionId) {
078                Validate.notNull(theLanguage, "theLanguage must not be null");
079
080                myNamespace = theNamespace;
081                mySql = theSql;
082                myParams = Collections.unmodifiableList(theParams);
083                myQueryTimestamp = theQueryTimestamp;
084                myElapsedTime = theElapsedTime;
085                myStackTrace = theStackTraceElements;
086                mySize = theSize;
087                myLanguage = theLanguage;
088                myRequestPartitionId = theRequestPartitionId;
089        }
090
091        public RequestPartitionId getRequestPartitionId() {
092                return myRequestPartitionId;
093        }
094
095        public String getNamespace() {
096                return myNamespace;
097        }
098
099        public long getQueryTimestamp() {
100                return myQueryTimestamp;
101        }
102
103        public long getElapsedTime() {
104                return myElapsedTime;
105        }
106
107        public String getThreadName() {
108                return myThreadName;
109        }
110
111        public String getSql(boolean theInlineParams, boolean theFormat) {
112                return getSql(theInlineParams, theFormat, false);
113        }
114
115        public LanguageEnum getLanguage() {
116                return myLanguage;
117        }
118
119        public String getSql(boolean theInlineParams, boolean theFormat, boolean theSanitizeParams) {
120                String retVal = mySql;
121                if (theFormat) {
122                        if (getLanguage() == LanguageEnum.SQL) {
123                                retVal = new BasicFormatterImpl().format(retVal);
124
125                                // BasicFormatterImpl annoyingly adds a newline at the very start of its output
126                                while (retVal.startsWith("\n")) {
127                                        retVal = retVal.substring(1);
128                                }
129                        }
130                }
131
132                if (theInlineParams) {
133                        List<String> nextParams = new ArrayList<>(myParams);
134                        int idx = 0;
135                        while (nextParams.size() > 0) {
136                                idx = retVal.indexOf("?", idx);
137                                if (idx == -1) {
138                                        break;
139                                }
140                                String nextParamValue = nextParams.remove(0);
141                                String nextSubstitution;
142                                if (nextParamValue != null) {
143                                        if (theSanitizeParams) {
144                                                nextParamValue = UrlUtil.sanitizeUrlPart(nextParamValue);
145                                        }
146                                        nextSubstitution = "'" + nextParamValue + "'";
147                                } else {
148                                        nextSubstitution = "NULL";
149                                }
150                                retVal = retVal.substring(0, idx) + nextSubstitution + retVal.substring(idx + 1);
151                                idx += nextSubstitution.length();
152                        }
153                }
154
155                return trim(retVal);
156        }
157
158        public StackTraceElement[] getStackTrace() {
159                return myStackTrace;
160        }
161
162        public int getSize() {
163                return mySize;
164        }
165
166        @Override
167        public String toString() {
168                return getSql(true, true);
169        }
170
171        public enum LanguageEnum {
172                SQL,
173                JSON
174        }
175}