Skip to content

Commit cdf0747

Browse files
committed
Add authentication data capture for CREATE/ALTER USER statements
- Add AuthenticationValues field to CreateQuery for storing password/hash values - Update parseCreateUser and parseAlterUser to capture BY 'value' expressions - Update explain output to show AuthenticationData with literal children - Use token.BY and token.WITH keywords instead of IDENT checks Fixes authentication data capture in EXPLAIN AST output for CREATE USER and ALTER USER statements with IDENTIFIED WITH method BY 'value' syntax.
1 parent 7080f64 commit cdf0747

6 files changed

Lines changed: 104 additions & 28 deletions

File tree

ast/ast.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ type CreateQuery struct {
272272
CreateUser bool `json:"create_user,omitempty"`
273273
AlterUser bool `json:"alter_user,omitempty"`
274274
HasAuthenticationData bool `json:"has_authentication_data,omitempty"`
275+
AuthenticationValues []string `json:"authentication_values,omitempty"` // Password/hash values from IDENTIFIED BY
275276
CreateDictionary bool `json:"create_dictionary,omitempty"`
276277
DictionaryAttrs []*DictionaryAttributeDeclaration `json:"dictionary_attrs,omitempty"`
277278
DictionaryDef *DictionaryDefinition `json:"dictionary_def,omitempty"`

internal/explain/statements.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,17 @@ func explainCreateQuery(sb *strings.Builder, n *ast.CreateQuery, indent string,
9595
if n.CreateUser || n.AlterUser {
9696
if n.HasAuthenticationData {
9797
fmt.Fprintf(sb, "%sCreateUserQuery (children 1)\n", indent)
98-
fmt.Fprintf(sb, "%s AuthenticationData\n", indent)
98+
// AuthenticationData has children if there are auth values
99+
if len(n.AuthenticationValues) > 0 {
100+
fmt.Fprintf(sb, "%s AuthenticationData (children %d)\n", indent, len(n.AuthenticationValues))
101+
for _, val := range n.AuthenticationValues {
102+
// Escape the value - strings need \' escaping
103+
escaped := escapeStringLiteral(val)
104+
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, escaped)
105+
}
106+
} else {
107+
fmt.Fprintf(sb, "%s AuthenticationData\n", indent)
108+
}
99109
} else {
100110
fmt.Fprintf(sb, "%sCreateUserQuery\n", indent)
101111
}

parser/parser.go

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,20 +1895,61 @@ func (p *Parser) parseCreateUser(create *ast.CreateQuery) {
18951895
p.nextToken()
18961896
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "IDENTIFIED" {
18971897
create.HasAuthenticationData = true
1898+
p.nextToken()
18981899
}
18991900
continue
19001901
}
19011902
// Check for IDENTIFIED (without NOT)
19021903
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "IDENTIFIED" {
19031904
create.HasAuthenticationData = true
1905+
p.nextToken()
1906+
// Parse authentication method and value
1907+
// Forms: IDENTIFIED BY 'password'
1908+
// IDENTIFIED WITH method BY 'password'
1909+
// IDENTIFIED WITH method BY 'password', method BY 'password', ...
1910+
for {
1911+
// Skip WITH if present (auth method follows)
1912+
if p.currentIs(token.WITH) {
1913+
p.nextToken()
1914+
}
1915+
// Skip auth method name (plaintext_password, sha256_password, etc.)
1916+
// Stop at BY (token), comma, or next section keywords
1917+
for p.currentIs(token.IDENT) {
1918+
ident := strings.ToUpper(p.current.Value)
1919+
// Stop at HOST, SETTINGS, DEFAULT, GRANTEES - don't consume these
1920+
if ident == "HOST" || ident == "SETTINGS" || ident == "DEFAULT" || ident == "GRANTEES" {
1921+
break
1922+
}
1923+
p.nextToken()
1924+
// Handle REALM/SERVER string values (for kerberos/ldap)
1925+
if p.currentIs(token.STRING) && (ident == "REALM" || ident == "SERVER") {
1926+
p.nextToken()
1927+
}
1928+
}
1929+
// Check for BY 'value' (BY is a keyword token, not IDENT)
1930+
if p.currentIs(token.BY) {
1931+
p.nextToken()
1932+
if p.currentIs(token.STRING) {
1933+
create.AuthenticationValues = append(create.AuthenticationValues, p.current.Value)
1934+
p.nextToken()
1935+
}
1936+
}
1937+
// Check for comma (multiple auth methods)
1938+
if p.currentIs(token.COMMA) {
1939+
p.nextToken()
1940+
continue
1941+
}
1942+
break
1943+
}
1944+
continue
19041945
}
19051946
p.nextToken()
19061947
}
19071948
}
19081949

19091950
func (p *Parser) parseAlterUser() *ast.CreateQuery {
19101951
create := &ast.CreateQuery{
1911-
Position: p.current.Pos,
1952+
Position: p.current.Pos,
19121953
CreateUser: true,
19131954
AlterUser: true,
19141955
}
@@ -1919,8 +1960,55 @@ func (p *Parser) parseAlterUser() *ast.CreateQuery {
19191960
// Parse user name
19201961
create.UserName = p.parseIdentifierName()
19211962

1922-
// Skip the rest of the user definition (complex syntax)
1963+
// Scan for authentication data (NOT IDENTIFIED or IDENTIFIED)
19231964
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
1965+
// Check for NOT IDENTIFIED
1966+
if p.currentIs(token.NOT) {
1967+
p.nextToken()
1968+
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "IDENTIFIED" {
1969+
create.HasAuthenticationData = true
1970+
p.nextToken()
1971+
}
1972+
continue
1973+
}
1974+
// Check for IDENTIFIED (without NOT)
1975+
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "IDENTIFIED" {
1976+
create.HasAuthenticationData = true
1977+
p.nextToken()
1978+
// Parse authentication method and value
1979+
for {
1980+
// Skip WITH if present
1981+
if p.currentIs(token.WITH) {
1982+
p.nextToken()
1983+
}
1984+
// Skip auth method name
1985+
for p.currentIs(token.IDENT) {
1986+
ident := strings.ToUpper(p.current.Value)
1987+
if ident == "HOST" || ident == "SETTINGS" || ident == "DEFAULT" || ident == "GRANTEES" {
1988+
break
1989+
}
1990+
p.nextToken()
1991+
if p.currentIs(token.STRING) && (ident == "REALM" || ident == "SERVER") {
1992+
p.nextToken()
1993+
}
1994+
}
1995+
// Check for BY 'value'
1996+
if p.currentIs(token.BY) {
1997+
p.nextToken()
1998+
if p.currentIs(token.STRING) {
1999+
create.AuthenticationValues = append(create.AuthenticationValues, p.current.Value)
2000+
p.nextToken()
2001+
}
2002+
}
2003+
// Check for comma (multiple auth methods)
2004+
if p.currentIs(token.COMMA) {
2005+
p.nextToken()
2006+
continue
2007+
}
2008+
break
2009+
}
2010+
continue
2011+
}
19242012
p.nextToken()
19252013
}
19262014

parser/testdata/01292_create_user/metadata.json

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,11 @@
44
"stmt158": true,
55
"stmt159": true,
66
"stmt160": true,
7-
"stmt169": true,
8-
"stmt171": true,
97
"stmt196": true,
108
"stmt197": true,
119
"stmt200": true,
1210
"stmt201": true,
13-
"stmt205": true,
14-
"stmt207": true,
15-
"stmt208": true,
16-
"stmt22": true,
1711
"stmt221": true,
18-
"stmt23": true,
19-
"stmt239": true,
20-
"stmt24": true,
21-
"stmt25": true,
22-
"stmt26": true,
23-
"stmt27": true,
24-
"stmt28": true,
25-
"stmt29": true,
26-
"stmt39": true,
27-
"stmt40": true,
28-
"stmt41": true,
29-
"stmt42": true,
30-
"stmt43": true
12+
"stmt239": true
3113
}
3214
}

parser/testdata/01702_system_query_log/metadata.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"explain_todo": {
3-
"stmt15": true,
43
"stmt19": true,
54
"stmt24": true,
65
"stmt28": true,
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt2": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)