// Package pgast wraps go-pgquery (libpg_query compiled to WASM via wazero, no // cgo) to produce a PostgreSQL parse tree from a SQL string. // // The concrete node types live in github.com/pganalyze/pg_query_go/v6 and are // generated from the PostgreSQL source; callers import that package directly // when walking the tree. package pgast import ( "strings" pg_query "github.com/pganalyze/pg_query_go/v6" waspg "github.com/wasilibs/go-pgquery" ) // ParseResult is re-exported so callers don't need to import both packages. type ParseResult = pg_query.ParseResult // Parse parses sql and returns the PostgreSQL parse tree. The returned error // is a pg_query parse error containing a message and location; it is safe to // display to the user. func Parse(sql string) (*ParseResult, error) { return waspg.Parse(sql) } // FirstTokenOffset advances past whitespace and SQL comments (-- line and // /* block */) from start, returning the byte offset of the first real token. // This is needed because RawStmt.StmtLocation points to the start of the // statement "block" which includes any preceding comments, not to the first // keyword token. func FirstTokenOffset(sql string, start int) int { i := start n := len(sql) for i < n { switch { case sql[i] == ' ' || sql[i] == '\t' || sql[i] == '\r' || sql[i] == '\n': i++ case i+1 < n && sql[i] == '-' && sql[i+1] == '-': for i < n && sql[i] != '\n' { i++ } case i+1 < n && sql[i] == '/' && sql[i+1] == '*': i += 2 for i+1 < n && !(sql[i] == '*' && sql[i+1] == '/') { i++ } if i+1 < n { i += 2 } default: return i } } return start } // LocationToLineCol converts a 0-based byte offset into a 1-based (line, col) // pair. Returns (1, 1) for a negative offset. func LocationToLineCol(sql string, offset int) (line, col int) { if offset < 0 { return 1, 1 } if offset > len(sql) { offset = len(sql) } before := sql[:offset] line = strings.Count(before, "\n") + 1 lastNL := strings.LastIndex(before, "\n") if lastNL < 0 { col = offset + 1 } else { col = offset - lastNL } return line, col }