Skip to content

Commit

Permalink
Merge pull request #15 from future-architect/feature/multiple-in-bind-v2
Browse files Browse the repository at this point in the history
in 句バインドの複数指定対応
  • Loading branch information
ma91n authored May 25, 2022
2 parents 8c12716 + 98fd2bc commit 946b063
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 20 deletions.
45 changes: 40 additions & 5 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,24 @@ func build(tokens []token, inputParams map[string]interface{}) (string, []interf
for _, token := range tokens {
if token.kind == tkBind {
if elem, ok := inputParams[token.value]; ok {
switch slice := elem.(type) {
switch elemTyp := elem.(type) {
case []string:
token.str = bindLiterals(token.str, len(slice))
for _, value := range slice {
token.str = bindLiterals(token.str, len(elemTyp))
for _, value := range elemTyp {
params = append(params, value)
}
case []int:
token.str = bindLiterals(token.str, len(slice))
for _, value := range slice {
token.str = bindLiterals(token.str, len(elemTyp))
for _, value := range elemTyp {
params = append(params, value)
}
case [][]interface{}:
token.str = bindTable(token.str, len(elemTyp), len(elemTyp[0]))
for _, rows := range elemTyp {
for _, columns := range rows {
params = append(params, columns)
}
}
default:
params = append(params, elem)
}
Expand Down Expand Up @@ -92,6 +99,34 @@ func bindLiterals(str string, number int) string {
return fmt.Sprint(b.String(), str)
}

func bindTable(str string, rowNumber, columnNumber int) string {
str = strings.TrimLeftFunc(str, func(r rune) bool {
return r != unicode.SimpleFold('/')
})

var column strings.Builder
column.WriteRune('(')
for i := 0; i < columnNumber; i++ {
column.WriteRune('?')
if i != columnNumber-1 {
column.WriteString(", ")
}
}
column.WriteRune(')')

var row strings.Builder
row.WriteRune('(')
for i := 0; i < rowNumber; i++ {
row.WriteString(column.String())
if i != rowNumber-1 {
row.WriteString(", ")
}
}
row.WriteRune(')')

return fmt.Sprint(row.String(), str)
}

func formatQuery(query string) string {
query = strings.ReplaceAll(query, "\n", " ")
query = strings.ReplaceAll(query, "\t", " ")
Expand Down
73 changes: 60 additions & 13 deletions eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import (
)

type Info struct {
Name string `twowaysql:"name"`
EmpNo int `twowaysql:"EmpNo"`
MaxEmpNo int `twowaysql:"maxEmpNo"`
DeptNo int `twowaysql:"deptNo"`
FirstName string `twowaysql:"firstName"`
LastName string `twowaysql:"lastName"`
Email string `twowaysql:"email"`
GenderList []string `twowaysql:"gender_list"`
IntList []int `twowaysql:"int_list"`
Checked bool `twowaysql:"checked"`
Unchecked bool `twowaysql:"unchecked"`
Nil interface{} `twowaysql:"nil"`
Zero int `twowaysql:"zero"`
Name string `twowaysql:"name"`
EmpNo int `twowaysql:"EmpNo"`
MaxEmpNo int `twowaysql:"maxEmpNo"`
DeptNo int `twowaysql:"deptNo"`
FirstName string `twowaysql:"firstName"`
LastName string `twowaysql:"lastName"`
Email string `twowaysql:"email"`
GenderList []string `twowaysql:"gender_list"`
IntList []int `twowaysql:"int_list"`
Checked bool `twowaysql:"checked"`
Unchecked bool `twowaysql:"unchecked"`
Nil interface{} `twowaysql:"nil"`
Zero int `twowaysql:"zero"`
Table [][]interface{} `twowaysql:"table"`
}

func TestEval(t *testing.T) {
Expand Down Expand Up @@ -179,6 +180,29 @@ func TestEval(t *testing.T) {
"F",
},
},
{
name: "in bind table",
input: `SELECT * FROM person WHERE employee_no = /*maxEmpNo*/1000 /* IF table !== undefined */ AND (person.gender, person.Name) in /*table*/(('M', 'Jeff'), ('F', 'Jeff')) /* END */`,
inputParams: Info{
Name: "Jeff",
MaxEmpNo: 3,
DeptNo: 12,
GenderList: []string{"M", "F"},
IntList: []int{1, 2, 3},
Table: [][]interface{}{
{"M", "Tom"},
{"F", "Tom"},
},
},
wantQuery: `SELECT * FROM person WHERE employee_no = ?/*maxEmpNo*/ AND (person.gender, person.Name) in ((?, ?), (?, ?))/*table*/`,
wantParams: []interface{}{
3,
"M",
"Tom",
"F",
"Tom",
},
},
{
name: "in bind string",
input: `SELECT * FROM person WHERE employee_no = /*maxEmpNo*/1000 /* IF int_list !== null */ AND person.gender in /*int_list*/(3,5,7) /* END */`,
Expand Down Expand Up @@ -881,6 +905,29 @@ func TestEvalWithMap(t *testing.T) {
wantQuery: `SELECT * FROM person WHERE employee_no < ?/*maxEmpNo*/`,
wantParams: []interface{}{3},
},
{
name: "bind nil parameter",
input: `SELECT * FROM person WHERE 1=1 /* IF genderList !== undefined */ AND gender_list IN /*genderList*/('M', 'F') /* END */`,
inputParams: map[string]interface{}{
"genderList": nil,
},
wantQuery: `SELECT * FROM person WHERE 1=1`,
wantParams: []interface{}{},
},
{
name: "bind table parameter",
input: `SELECT * FROM person WHERE name = /*name*/'name' AND (a, b) IN /*table*/(('x', 10), ('y', 11))`,
inputParams: map[string]interface{}{
"name": "Jeff",
"table": [][]interface{}{
{"a", 1},
{"b", 2},
{"c", 3},
},
},
wantQuery: `SELECT * FROM person WHERE name = ?/*name*/ AND (a, b) IN ((?, ?), (?, ?), (?, ?))/*table*/`,
wantParams: []interface{}{"Jeff", "a", 1, "b", 2, "c", 3},
},
}

for _, tt := range tests {
Expand Down
18 changes: 16 additions & 2 deletions tokenizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,23 @@ func tokenize(str string) ([]token, error) {
if tok.kind == 0 {
tok.kind = tkBind
if quote := str[index]; quote == '(' {
// /* ... */( ... )
// /* ... */( ... ) or /* ... */( (...), (...) )
var quoteStack []interface{}
index++
for index < length && str[index] != ')' {
for index < length {
if str[index] == '(' {
quoteStack = append(quoteStack, struct{}{})
index++
continue
}
if str[index] == ')' {
if len(quoteStack) == 0 {
break
}
quoteStack = quoteStack[0 : len(quoteStack)-1]
index++
continue
}
index++
}
if str[index] != ')' {
Expand Down
35 changes: 35 additions & 0 deletions tokenizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,41 @@ func TestTokenize(t *testing.T) {
},
},
},
{
name: "multiple in bind",
input: `SELECT * FROM person /* IF gender_list !== null */ WHERE (person.gender, person.firstName) in /*table*/('M', 'Jeff') /* END */`,
want: []token{
{
kind: tkSQLStmt,
str: "SELECT * FROM person ",
},
{
kind: tkIf,
str: "/* IF gender_list !== null */",
condition: "gender_list !== null",
},
{
kind: tkSQLStmt,
str: " WHERE (person.gender, person.firstName) in ",
},
{
kind: tkBind,
str: "?/*table*/",
value: "table",
},
{
kind: tkSQLStmt,
str: " ",
},
{
kind: tkEnd,
str: "/* END */",
},
{
kind: tkEndOfProgram,
},
},
},
}

for _, tt := range tests {
Expand Down

0 comments on commit 946b063

Please sign in to comment.