github.com/XiaoMi/Gaea@v1.2.5/parser/comments.go (about) 1 /* 2 Copyright 2017 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package parser 18 19 import ( 20 "strings" 21 "unicode" 22 ) 23 24 func isNonSpace(r rune) bool { 25 return !unicode.IsSpace(r) 26 } 27 28 // leadingCommentEnd returns the first index after all leading comments, or 29 // 0 if there are no leading comments. 30 func leadingCommentEnd(text string) (end int) { 31 hasComment := false 32 pos := 0 33 for pos < len(text) { 34 // Eat up any whitespace. Trailing whitespace will be considered part of 35 // the leading comments. 36 nextVisibleOffset := strings.IndexFunc(text[pos:], isNonSpace) 37 if nextVisibleOffset < 0 { 38 break 39 } 40 pos += nextVisibleOffset 41 remainingText := text[pos:] 42 43 // Found visible characters. Look for '/*' at the beginning 44 // and '*/' somewhere after that. 45 if len(remainingText) < 4 || remainingText[:2] != "/*" { 46 break 47 } 48 commentLength := 4 + strings.Index(remainingText[2:], "*/") 49 if commentLength < 4 { 50 // Missing end comment :/ 51 break 52 } 53 54 hasComment = true 55 pos += commentLength 56 } 57 58 if hasComment { 59 return pos 60 } 61 return 0 62 } 63 64 // trailingCommentStart returns the first index of trailing comments. 65 // If there are no trailing comments, returns the length of the input string. 66 func trailingCommentStart(text string) (start int) { 67 hasComment := false 68 reducedLen := len(text) 69 for reducedLen > 0 { 70 // Eat up any whitespace. Leading whitespace will be considered part of 71 // the trailing comments. 72 nextReducedLen := strings.LastIndexFunc(text[:reducedLen], isNonSpace) + 1 73 if nextReducedLen == 0 { 74 break 75 } 76 reducedLen = nextReducedLen 77 if reducedLen < 4 || text[reducedLen-2:reducedLen] != "*/" { 78 break 79 } 80 81 // Find the beginning of the comment 82 startCommentPos := strings.LastIndex(text[:reducedLen-2], "/*") 83 if startCommentPos < 0 { 84 // Badly formatted sql :/ 85 break 86 } 87 88 hasComment = true 89 reducedLen = startCommentPos 90 } 91 92 if hasComment { 93 return reducedLen 94 } 95 return len(text) 96 } 97 98 // MarginComments holds the leading and trailing comments that surround a query. 99 type MarginComments struct { 100 Leading string 101 Trailing string 102 } 103 104 // SplitMarginComments pulls out any leading or trailing comments from a raw sql query. 105 // This function also trims leading (if there's a comment) and trailing whitespace. 106 func SplitMarginComments(sql string) (query string, comments MarginComments) { 107 trailingStart := trailingCommentStart(sql) 108 leadingEnd := leadingCommentEnd(sql[:trailingStart]) 109 comments = MarginComments{ 110 Leading: strings.TrimLeftFunc(sql[:leadingEnd], unicode.IsSpace), 111 Trailing: strings.TrimRightFunc(sql[trailingStart:], unicode.IsSpace), 112 } 113 return strings.TrimFunc(sql[leadingEnd:trailingStart], unicode.IsSpace), comments 114 } 115 116 // StripLeadingComments trims the SQL string and removes any leading comments 117 func StripLeadingComments(sql string) string { 118 sql = strings.TrimFunc(sql, unicode.IsSpace) 119 120 for hasCommentPrefix(sql) { 121 switch sql[0] { 122 case '/': 123 // Multi line comment 124 index := strings.Index(sql, "*/") 125 if index <= 1 { 126 return sql 127 } 128 // don't strip /*! ... */ or /*!50700 ... */ 129 if len(sql) > 2 && sql[2] == '!' { 130 return sql 131 } 132 sql = sql[index+2:] 133 case '-': 134 // Single line comment 135 index := strings.Index(sql, "\n") 136 if index == -1 { 137 return "" 138 } 139 sql = sql[index+1:] 140 } 141 142 sql = strings.TrimFunc(sql, unicode.IsSpace) 143 } 144 145 return sql 146 } 147 148 func hasCommentPrefix(sql string) bool { 149 return len(sql) > 1 && ((sql[0] == '/' && sql[1] == '*') || (sql[0] == '-' && sql[1] == '-')) 150 } 151 152 // CommentDirectives is the parsed representation for execution directives 153 // conveyed in query comments 154 type CommentDirectives map[string]interface{} 155 156 // IsSet checks the directive map for the named directive and returns 157 // true if the directive is set and has a true/false or 0/1 value 158 func (d CommentDirectives) IsSet(key string) bool { 159 if d == nil { 160 return false 161 } 162 163 val, ok := d[key] 164 if !ok { 165 return false 166 } 167 168 boolVal, ok := val.(bool) 169 if ok { 170 return boolVal 171 } 172 173 intVal, ok := val.(int) 174 if ok { 175 return intVal == 1 176 } 177 return false 178 }