package sqlite import ( "regexp" "strings" ) var generatedAsRegex = regexp.MustCompile(`(?is)\bAS\s*\(`) // parseGeneratedExpression extracts the expression of a generated column from a CREATE // TABLE statement, e.g. `full TEXT GENERATED ALWAYS AS (a || b) STORED` yields `a || b`. // It returns "" when the column or its expression cannot be found. func parseGeneratedExpression(createSQL, columnName string) string { open := strings.Index(createSQL, "(") if open < 0 { return "" } for _, def := range splitTopLevel(createSQL[open+1:]) { if !strings.EqualFold(firstIdentifier(def), columnName) { continue } loc := generatedAsRegex.FindStringIndex(def) if loc == nil { return "" } return balancedParens(def[loc[1]-1:]) } return "" } // splitTopLevel splits a table body on commas that are outside parentheses and quotes, // stopping at the parenthesis that closes the body. func splitTopLevel(body string) []string { var parts []string depth := 0 var quote byte start := 0 for i := 0; i < len(body); i++ { ch := body[i] if quote != 0 { if ch == quote { quote = 0 } continue } switch ch { case '\'', '"', '`': quote = ch case '[': quote = ']' case '(': depth++ case ')': if depth == 0 { return append(parts, body[start:i]) } depth-- case ',': if depth == 0 { parts = append(parts, body[start:i]) start = i + 1 } } } return append(parts, body[start:]) } // firstIdentifier returns the first (possibly quoted) identifier of a column definition. func firstIdentifier(def string) string { def = strings.TrimSpace(def) if def == "" { return "" } switch def[0] { case '"', '\'', '`': if end := strings.IndexByte(def[1:], def[0]); end >= 0 { return def[1 : 1+end] } case '[': if end := strings.IndexByte(def, ']'); end >= 0 { return def[1:end] } } if end := strings.IndexAny(def, " \t\r\n"); end >= 0 { return def[:end] } return def } // balancedParens returns the text inside the parenthesis group that starts at s[0]. func balancedParens(s string) string { depth := 0 var quote byte for i := 0; i < len(s); i++ { ch := s[i] if quote != 0 { if ch == quote { quote = 0 } continue } switch ch { case '\'', '"': quote = ch case '(': depth++ case ')': depth-- if depth == 0 { return strings.TrimSpace(s[1:i]) } } } return "" }