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  }