Skip to content

Commit

Permalink
Merge pull request #4 from actiontech/issue_442
Browse files Browse the repository at this point in the history
Issue 442
  • Loading branch information
sjjian authored Apr 7, 2022
2 parents 1c4141e + 53012e7 commit 4495db4
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 0 deletions.
17 changes: 17 additions & 0 deletions ast/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,20 @@ func (m *Mapper) GetStmt(ctx *Context) (string, error) {
}
return strings.TrimSuffix(buff.String(), "\n"), nil
}

func (m *Mapper) GetStmts(ctx *Context, skipErrorQuery bool) ([]string, error) {
var stmts []string
ctx.Sqls = m.SqlNodes
for _, a := range m.QueryNodes {
data, err := a.GetStmt(ctx)
if err == nil {
stmts = append(stmts, data)
continue
}
if skipErrorQuery {
continue
}
return nil, err
}
return stmts, nil
}
25 changes: 25 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package parser

import (
"encoding/xml"
"fmt"
"io"
"strings"

"github.com/actiontech/mybatis-mapper-2-sql/ast"
)

// ParseXML is a parser for parse all query in XML to string.
func ParseXML(data string) (string, error) {
r := strings.NewReader(data)
d := xml.NewDecoder(r)
Expand All @@ -25,6 +27,29 @@ func ParseXML(data string) (string, error) {
return stmt, nil
}

// ParseXMLQuery is a parser for parse all query in XML to []string one by one;
// you can set `skipErrorQuery` true to ignore invalid query.
func ParseXMLQuery(data string, skipErrorQuery bool) ([]string, error) {
r := strings.NewReader(data)
d := xml.NewDecoder(r)
n, err := parse(d, nil)
if err != nil {
return nil, err
}
if n == nil {
return nil, nil
}
m, ok := n.(*ast.Mapper)
if !ok {
return nil, fmt.Errorf("the mapper is not found")
}
stmts, err := m.GetStmts(ast.NewContext(), skipErrorQuery)
if err != nil {
return nil, err
}
return stmts, nil
}

func parse(d *xml.Decoder, start *xml.StartElement) (node ast.Node, err error) {
if start != nil {
node, err = scan(start)
Expand Down
260 changes: 260 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,3 +572,263 @@ func TestParserSQLRefIdNotFound(t *testing.T) {
t.Errorf("actual error is [%s]", err.Error())
}
}

func testParserQuery(t *testing.T, skipError bool, xmlData string, expect []string) {
actual, err := ParseXMLQuery(xmlData, skipError)
if err != nil {
t.Errorf("parse error: %v", err)
return
}
if len(actual) != len(expect) {
t.Errorf("the length of actual is not the same as the length of expected, actual length is %d, expect is %d",
len(actual), len(expect))
return
}
for i := range actual {
if actual[i] != expect[i] {
t.Errorf("\nexpect[%d]: [%s]\nactual[%d]: [%s]", i, expect, i, actual)
}
}

}

func TestParserQueryFullFile(t *testing.T) {
testParserQuery(t, false,
`
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Test">
<sql id="sometable">
fruits
</sql>
<sql id="somewhere">
WHERE
category = #{category}
</sql>
<sql id="someinclude">
FROM
<include refid="${include_target}"/>
<include refid="somewhere"/>
</sql>
<select id="testParameters">
SELECT
name,
category,
price
FROM
fruits
WHERE
category = #{category}
AND price > ${price}
</select>
<select id="testInclude">
SELECT
name,
category,
price
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
<select id="testIf">
SELECT
name,
category,
price
FROM
fruits
WHERE
1=1
<if test="category != null and category !=''">
AND category = #{category}
</if>
<if test="price != null and price !=''">
AND price = ${price}
<if test="price >= 400">
AND name = 'Fuji'
</if>
</if>
</select>
<select id="testTrim">
SELECT
name,
category,
price
FROM
fruits
<trim prefix="WHERE" prefixOverrides="AND|OR">
OR category = 'apple'
OR price = 200
</trim>
</select>
<select id="testWhere">
SELECT
name,
category,
price
FROM
fruits
<where>
AND category = 'apple'
<if test="price != null and price !=''">
AND price = ${price}
</if>
</where>
</select>
<update id="testSet">
UPDATE
fruits
<set>
<if test="category != null and category !=''">
category = #{category},
</if>
<if test="price != null and price !=''">
price = ${price},
</if>
</set>
WHERE
name = #{name}
</update>
<select id="testChoose">
SELECT
name,
category,
price
FROM
fruits
<where>
<choose>
<when test="name != null">
AND name = #{name}
</when>
<when test="category == 'banana'">
AND category = #{category}
<if test="price != null and price !=''">
AND price = ${price}
</if>
</when>
<otherwise>
AND category = 'apple'
</otherwise>
</choose>
</where>
</select>
<select id="testForeach">
SELECT
name,
category,
price
FROM
fruits
<where>
category = 'apple' AND
<foreach collection="apples" item="name" open="(" close=")" separator="OR">
<if test="name == 'Jonathan' or name == 'Fuji'">
name = #{name}
</if>
</foreach>
</where>
</select>
<insert id="testInsertMulti">
INSERT INTO
fruits
(
name,
category,
price
)
VALUES
<foreach collection="fruits" item="fruit" separator=",">
(
#{fruit.name},
#{fruit.category},
${fruit.price}
)
</foreach>
</insert>
<select id="testBind">
<bind name="likeName" value="'%' + name + '%'"/>
SELECT
name,
category,
price
FROM
fruits
WHERE
name like #{likeName}
</select>
</mapper>`,
[]string{
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `category`=? AND `price`>?",
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `category`=?",
"SELECT `name`,`category`,`price` FROM `fruits` WHERE 1=1 AND `category`=? AND `price`=? AND `name`=\"Fuji\"",
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `category`=\"apple\" OR `price`=200",
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `category`=\"apple\" AND `price`=?",
"UPDATE `fruits` SET `category`=?, `price`=? WHERE `name`=?",
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `name`=? AND `category`=? AND `price`=? AND `category`=\"apple\"",
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `category`=\"apple\" AND (`name`=? OR `name`=?)",
"INSERT INTO `fruits` (`name`,`category`,`price`) VALUES (?,?,?),(?,?,?)",
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `name` LIKE ?",
})
}

func TestParserQueryHasInvalidQuery(t *testing.T) {
_, err := ParseXMLQuery(
`
<mapper namespace="Test">
<sql id="someinclude">
*
</sql>
<select id="testBind">
<bind name="likeName" value="'%' + name + '%'"/>
SELECT
name,
category,
price
FROM
fruits
WHERE
name like #{likeName}
</select>
<select id="select" resultType="map">
select
<include refid="someinclude2" />
from t
</select>
</mapper>`, false)
if err == nil {
t.Errorf("expect has error, but no error")
}
if err.Error() != "sql someinclude2 is not exist" {
t.Errorf("actual error is [%s]", err.Error())
}
}

func TestParserQueryHasInvalidQueryButSkip(t *testing.T) {
testParserQuery(t, true,
`
<mapper namespace="Test">
<sql id="someinclude">
*
</sql>
<select id="testBind">
<bind name="likeName" value="'%' + name + '%'"/>
SELECT
name,
category,
price
FROM
fruits
WHERE
name like #{likeName}
</select>
<select id="select" resultType="map">
select
<include refid="someinclude2" />
from t
</select>
</mapper>`, []string{
"SELECT `name`,`category`,`price` FROM `fruits` WHERE `name` LIKE ?",
})
}

0 comments on commit 4495db4

Please sign in to comment.