feat: succesfully parse a simple select statement
This commit is contained in:
parent
d3f9600d58
commit
c79fb6f165
37
.aiContexts/SQL_Token_Types.md
Normal file
37
.aiContexts/SQL_Token_Types.md
Normal file
@ -0,0 +1,37 @@
|
||||
These are the SQL Token Types that will serve as the foundation on how we interpret SQL strings
|
||||
and create our `Query` structs
|
||||
|
||||
```go
|
||||
type TokenType int
|
||||
|
||||
const (
|
||||
ERROR TokenType = iota // 0
|
||||
EOF // 1
|
||||
SPACE // 2 space or newline
|
||||
STRING // 3 string literal
|
||||
INCOMPLETE_STRING // 4 incomplete string literal so that we can obfuscate it, e.g. 'abc
|
||||
NUMBER // 5 number literal
|
||||
IDENT // 6 identifier column name
|
||||
QUOTED_IDENT // 7 quoted identifier
|
||||
OPERATOR // 8 operator like = > < >= <=
|
||||
WILDCARD // 9 wildcard *
|
||||
COMMENT // 10 comment
|
||||
MULTILINE_COMMENT // 11 multiline comment
|
||||
PUNCTUATION // 12 punctuation such as a comma
|
||||
DOLLAR_QUOTED_FUNCTION // 13 dollar quoted function
|
||||
DOLLAR_QUOTED_STRING // 14 dollar quoted string
|
||||
POSITIONAL_PARAMETER // 15 numbered parameter
|
||||
BIND_PARAMETER // 16 bind parameter
|
||||
FUNCTION // 17 function
|
||||
SYSTEM_VARIABLE // 18 system variable
|
||||
UNKNOWN // 19 unknown token
|
||||
COMMAND // 20 SQL commands like SELECT INSERT UPDATE DELETE
|
||||
KEYWORD // 21 Other SQL keywords like FROM, WHERE, NOT, IS, LIKE
|
||||
JSON_OP // 22 JSON operators
|
||||
BOOLEAN // 23 boolean literal
|
||||
NULL // 24 null literal
|
||||
PROC_INDICATOR // 25 procedure indicator
|
||||
CTE_INDICATOR // 26 CTE indicator
|
||||
ALIAS_INDICATOR // 27 alias indicator
|
||||
)
|
||||
```
|
||||
89
main.go
89
main.go
@ -2,90 +2,31 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/DataDog/go-sqllexer"
|
||||
"query-inter/q"
|
||||
// "github.com/DataDog/go-sqllexer"
|
||||
)
|
||||
|
||||
type QueryType int
|
||||
|
||||
const (
|
||||
NONE QueryType = iota
|
||||
SELECT
|
||||
UPDATE
|
||||
INSERT
|
||||
DELETE
|
||||
)
|
||||
|
||||
type Query struct {
|
||||
Type QueryType
|
||||
FullSql string
|
||||
IsValid bool
|
||||
}
|
||||
|
||||
func IsTokenEndOfStatement(token *sqllexer.Token) bool {
|
||||
return (token.Type == sqllexer.EOF || token.Value == ";")
|
||||
}
|
||||
|
||||
func GetQueryTypeFromToken(token *sqllexer.Token) QueryType {
|
||||
if token.Type != sqllexer.COMMAND {
|
||||
return NONE
|
||||
}
|
||||
|
||||
var foundType QueryType
|
||||
switch strings.ToUpper(token.Value) {
|
||||
case "SELECT":
|
||||
foundType = SELECT
|
||||
case "UPDATE":
|
||||
foundType = UPDATE
|
||||
case "INSERT":
|
||||
foundType = INSERT
|
||||
case "DELETE":
|
||||
foundType = DELETE
|
||||
default:
|
||||
foundType = NONE
|
||||
}
|
||||
|
||||
return foundType
|
||||
}
|
||||
|
||||
func IsCrudSqlStatement(token *sqllexer.Token) bool {
|
||||
queryType := GetQueryTypeFromToken(token)
|
||||
return (queryType > 0 && queryType <= 4) // TODO: Update if QueryTypes Change
|
||||
}
|
||||
|
||||
func GetQueryTypeFromSql(sql string) QueryType {
|
||||
var queryType QueryType
|
||||
|
||||
lexer := sqllexer.New(sql)
|
||||
for {
|
||||
token := lexer.Scan()
|
||||
if IsTokenEndOfStatement(token) {
|
||||
break
|
||||
}
|
||||
|
||||
queryType = GetQueryTypeFromToken(token)
|
||||
if queryType > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return queryType
|
||||
}
|
||||
|
||||
func main() {
|
||||
query := "DELETE * FROM users \n WHERE id = something AND SELECT;"
|
||||
newQuery := GetQueryTypeFromSql(query)
|
||||
selectQuery := "SELECT id, name, createDate FROM users WHERE name=1;"
|
||||
|
||||
fmt.Println(newQuery)
|
||||
allStatements := q.ExtractSqlStatmentsFromString(selectQuery)
|
||||
fmt.Println(allStatements)
|
||||
|
||||
//lexer := sqllexer.New(query)
|
||||
//lexer := sqllexer.New(selectQuery)
|
||||
//for {
|
||||
// token := lexer.Scan()
|
||||
// fmt.Println(token.Value, token.Type)
|
||||
|
||||
//
|
||||
// if token.Type == sqllexer.EOF {
|
||||
// break
|
||||
// }
|
||||
//}
|
||||
|
||||
for _, sql := range allStatements {
|
||||
query := q.ParseSelectStatement(sql)
|
||||
//fmt.Print(i)
|
||||
//fmt.Println(query)
|
||||
fmt.Println(query.GetFullSql())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
130
q/query.go
Normal file
130
q/query.go
Normal file
@ -0,0 +1,130 @@
|
||||
package q
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/DataDog/go-sqllexer"
|
||||
)
|
||||
|
||||
type Query interface {
|
||||
GetFullSql() string
|
||||
}
|
||||
|
||||
type QueryType int
|
||||
|
||||
const (
|
||||
NONE QueryType = iota
|
||||
SELECT
|
||||
UPDATE
|
||||
INSERT
|
||||
DELETE
|
||||
)
|
||||
|
||||
type Conditional struct {
|
||||
Key string
|
||||
Operator string
|
||||
Value string
|
||||
DataType string
|
||||
Extension string // AND, OR, etc
|
||||
}
|
||||
|
||||
func GetQueryTypeFromToken(token *sqllexer.Token) QueryType {
|
||||
if token.Type != sqllexer.COMMAND {
|
||||
return NONE
|
||||
}
|
||||
|
||||
var foundType QueryType
|
||||
switch strings.ToUpper(token.Value) {
|
||||
case "SELECT":
|
||||
foundType = SELECT
|
||||
case "UPDATE":
|
||||
foundType = UPDATE
|
||||
case "INSERT":
|
||||
foundType = INSERT
|
||||
case "DELETE":
|
||||
foundType = DELETE
|
||||
default:
|
||||
foundType = NONE
|
||||
}
|
||||
|
||||
return foundType
|
||||
}
|
||||
|
||||
func IsCrudSqlStatement(token *sqllexer.Token) bool {
|
||||
queryType := GetQueryTypeFromToken(token)
|
||||
return (queryType > 0 && queryType <= 4) // TODO: Update if QueryTypes Change
|
||||
}
|
||||
|
||||
func IsTokenBeginingOfStatement(currentToken *sqllexer.Token, previousToken *sqllexer.Token) bool {
|
||||
return IsCrudSqlStatement(currentToken)
|
||||
}
|
||||
|
||||
func IsTokenEndOfStatement(token *sqllexer.Token) bool {
|
||||
return (token.Type == sqllexer.EOF || token.Value == ";")
|
||||
}
|
||||
|
||||
func GetQueryTypeFromSql(sql string) QueryType {
|
||||
var queryType QueryType
|
||||
|
||||
lexer := sqllexer.New(sql)
|
||||
for {
|
||||
token := lexer.Scan()
|
||||
if IsTokenEndOfStatement(token) {
|
||||
break
|
||||
}
|
||||
|
||||
queryType = GetQueryTypeFromToken(token)
|
||||
if queryType > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return queryType
|
||||
}
|
||||
|
||||
func ExtractSqlStatmentsFromString(sqlString string) []string {
|
||||
var foundStatments []string
|
||||
|
||||
var isBeginingFound = false
|
||||
var isEndingFound = false
|
||||
|
||||
var previousScannedToken sqllexer.Token
|
||||
var currentWorkingStatment = ""
|
||||
|
||||
lexer := sqllexer.New(sqlString)
|
||||
for {
|
||||
token := lexer.Scan()
|
||||
previousScannedToken = *token
|
||||
|
||||
if IsTokenEndOfStatement(token) {
|
||||
isEndingFound = true
|
||||
}
|
||||
|
||||
if isEndingFound {
|
||||
if strings.Trim(currentWorkingStatment, " ") != "" {
|
||||
foundStatments = append(foundStatments, currentWorkingStatment)
|
||||
}
|
||||
|
||||
isBeginingFound = false
|
||||
isEndingFound = false
|
||||
currentWorkingStatment = ""
|
||||
|
||||
if token.Type == sqllexer.EOF {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !isBeginingFound && IsTokenBeginingOfStatement(token, &previousScannedToken) { // TODO: add logic that checks if begining is already found, if so an error should happen before here
|
||||
isBeginingFound = true
|
||||
} else if !isBeginingFound {
|
||||
continue
|
||||
}
|
||||
|
||||
currentWorkingStatment = currentWorkingStatment + token.Value
|
||||
}
|
||||
|
||||
return foundStatments
|
||||
}
|
||||
123
q/select.go
Normal file
123
q/select.go
Normal file
@ -0,0 +1,123 @@
|
||||
package q
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/DataDog/go-sqllexer"
|
||||
)
|
||||
|
||||
type Select struct {
|
||||
Table string
|
||||
Columns []string
|
||||
Conditionals []Conditional
|
||||
IsWildcard bool
|
||||
}
|
||||
|
||||
func (q *Select) GetFullSql() string {
|
||||
var workingSqlSlice []string
|
||||
|
||||
workingSqlSlice = append(workingSqlSlice, "SELECT")
|
||||
|
||||
if q.IsWildcard {
|
||||
workingSqlSlice = append(workingSqlSlice, "*")
|
||||
} else {
|
||||
for i, column := range q.Columns {
|
||||
if i < (len(q.Columns) - 1) {
|
||||
workingSqlSlice = append(workingSqlSlice, column+",")
|
||||
} else {
|
||||
workingSqlSlice = append(workingSqlSlice, column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workingSqlSlice = append(workingSqlSlice, "FROM "+q.Table)
|
||||
|
||||
for _, condition := range q.Conditionals {
|
||||
workingSqlSlice = append(workingSqlSlice, condition.Key)
|
||||
workingSqlSlice = append(workingSqlSlice, condition.Operator)
|
||||
workingSqlSlice = append(workingSqlSlice, condition.Value) // TODO: need to account for `AND` and `OR`s and stuff
|
||||
}
|
||||
|
||||
fullSql := strings.Join(workingSqlSlice, " ")
|
||||
|
||||
return fullSql
|
||||
}
|
||||
|
||||
func ParseSelectStatement(sql string) Select {
|
||||
query := Select{}
|
||||
|
||||
passedSELECT := false
|
||||
passedColumns := false
|
||||
passedFROM := false
|
||||
passedTable := false
|
||||
passedWHERE := false
|
||||
|
||||
var workingConditional = Conditional{}
|
||||
|
||||
var columns []string
|
||||
lexer := sqllexer.New(sql)
|
||||
for {
|
||||
token := lexer.Scan()
|
||||
if IsTokenEndOfStatement(token) {
|
||||
break
|
||||
}
|
||||
|
||||
if !passedSELECT && strings.ToUpper(token.Value) != "SELECT" {
|
||||
break
|
||||
} else if !passedSELECT {
|
||||
passedSELECT = true
|
||||
continue
|
||||
}
|
||||
|
||||
if !passedColumns {
|
||||
if token.Type == sqllexer.WILDCARD {
|
||||
passedColumns = true
|
||||
columns = make([]string, 0)
|
||||
query.IsWildcard = true
|
||||
} else if token.Type == sqllexer.IDENT {
|
||||
columns = append(columns, token.Value)
|
||||
continue
|
||||
} else if token.Type == sqllexer.PUNCTUATION || token.Type == sqllexer.SPACE {
|
||||
continue
|
||||
} else {
|
||||
passedColumns = true // TODO: make sure that I should be doing this
|
||||
query.Columns = columns
|
||||
}
|
||||
}
|
||||
|
||||
if !passedFROM && strings.ToUpper(token.Value) == "FROM" {
|
||||
passedFROM = true
|
||||
} else if !passedFROM {
|
||||
continue // TODO: make sure to check for other keywords that are allowed
|
||||
}
|
||||
|
||||
if !passedTable && token.Type == sqllexer.IDENT {
|
||||
passedTable = true
|
||||
query.Table = token.Value
|
||||
} else if !passedTable {
|
||||
continue
|
||||
}
|
||||
|
||||
if !passedWHERE && strings.ToUpper(token.Value) == "WHERE" {
|
||||
passedWHERE = true
|
||||
} else if !passedWHERE {
|
||||
continue
|
||||
}
|
||||
|
||||
if token.Type == sqllexer.IDENT {
|
||||
workingConditional.Key = token.Value
|
||||
} else if token.Type == sqllexer.OPERATOR {
|
||||
workingConditional.Operator = token.Value
|
||||
} else if token.Type == sqllexer.BOOLEAN || token.Type == sqllexer.NULL || token.Type == sqllexer.STRING || token.Type == sqllexer.NUMBER {
|
||||
workingConditional.Value = token.Value
|
||||
} // TODO: add captire for data type
|
||||
|
||||
if workingConditional.Key != "" && workingConditional.Operator != "" && workingConditional.Value != "" {
|
||||
query.Conditionals = append(query.Conditionals, workingConditional)
|
||||
workingConditional = Conditional{}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user