package gocqlx import ( "bytes" "errors" "github.com/gocql/gocql" "strconv" "unicode" ) type Queryx struct { *gocql.Query names []string } // Allow digits and letters in bind params; additionally runes are // checked against underscores, meaning that bind params can have be // alphanumeric with underscores. Mind the difference between unicode // digits and numbers, where '5' is a digit but 'δΊ”' is not. var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit} // CompileNamedQuery compiles a named query into an unbound query using the // '?' bindvar and a list of names. func CompileNamedQuery(qs []byte) (cql string, names []string, err error) { // guess number of names n := bytes.Count(qs, []byte(":")) if n == 0 { return "", nil, errors.New("expected a named query") } names = make([]string, 0, n) rebound := make([]byte, 0, len(qs)) inName := false last := len(qs) - 1 name := make([]byte, 0, 10) for i, b := range qs { // a ':' while we're in a name is an error if b == ':' { // if this is the second ':' in a '::' escape sequence, append a ':' if inName && i > 0 && qs[i-1] == ':' { rebound = append(rebound, ':') inName = false continue } else if inName { err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i)) return cql, names, err } inName = true name = []byte{} // if we're in a name, and this is an allowed character, continue } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last { // append the byte to the name if we are in a name and not on the last byte name = append(name, b) // if we're in a name and it's not an allowed character, the name is done } else if inName { inName = false // if this is the final byte of the string and it is part of the name, then // make sure to add it to the name if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) { name = append(name, b) } // add the string representation to the names list names = append(names, string(name)) // add a proper bindvar for the bindType rebound = append(rebound, '?') // add this byte to string unless it was not part of the name if i != last { rebound = append(rebound, b) } else if !unicode.IsOneOf(allowedBindRunes, rune(b)) { rebound = append(rebound, b) } } else { // this is a normal byte and should just go onto the rebound query rebound = append(rebound, b) } } return string(rebound), names, err }