我想在Golang中使用Teng
我是TuneCore日本的新人工程师 skfvr。
这篇文章是Wano Group Advent Calendar 2019的第三天的文章。
本次将讲述如何用Golang自己编写一个类似Perl轻量级ORM工具” Teng “的故事。
请注意
-
- 本記事はPerl/Golangのいずれかを持ち上げる/持ち下げる目的の記事でないことを予めお断りしておきます。
-
- 業務とは関係ありません。
- 初投稿故、誤字/脱字/知識不足を暖かく見守っていただけると幸いです。知識不足の祭は指摘していただけると嬉しいです。
1. “Teng是什么意思?”
首先使用Teng。Teng是Perl中的一种轻量级ORM。例如,可以像下面这样简化部分代码的编写。
my $hoge = $teng->single('hoge_table',{
id => 1,
});
# => select * from hoge_table where id = 1
如果查询很简单,可以使用简洁的表达来描述,因此在想要打这样的查询时非常方便。此外,还可以直接编写并执行查询。
my $hoge = $teng->search_by_sql("select * from hoge_table where id = 1");
当我第一次使用它时,我想着“如果在Golang中也能像Teng一样写查询语句,那就太强大了”(感到惊讶)。
顺便提一下,在这个阶段,Go语言已经有了很多方便易用的ORM,比如”gorm”和”sqlx”等等,所以从实战角度来看,不太有必要再花费精力去开发一个自己的ORM,这样感觉上并没有太多优势。
2. 先提一下
我先介绍一下已经完成的东西(由于简化写作,请见谅)。
sql, args, err := dao.Select(model.TableNameHogeTable, model.HogeTableParam{
DeleteFlg: st.Statement{"=", false},
Name: st.Statement{"=", "hoge"},
}).SQL()
// => select * from hoge_table where delete_flg = 0 and name = "hoge";
sql, args, err := dao.Update(model.TableNameHogeTable, model.HogeTableSetter{
Name: st.Setter{"hoge"},
}, model.HogeTableParam{
ID: st.Statement{"=", 225},
})
// => update hoge_table set name = "hoge" where id = 225;
我竟然做出了一个具有我自我的东西…。
顺便说一下,在Golang中,我经常看到的查询生成器就是这样的。如果不忽略换行的话,这个版本更易读。
sql, args, err := generator.Select(TableNameHogeTable)
.Where("delete_flg", "=", 0)
.Where("name", "=", "hoge")
3. 紹介一下疲劳点。
我会介绍在制作过程中让人感觉很辛苦的一点。其中最辛苦的部分是”模具”。
形状
非常艰辛。
必须将MySQL的类型转换为Golang的类型。
在Perl中,只要掌握标量,哈希和数组这些基本概念,就可以应对各种情况,例如可以写成以下这样。
my $hoge = $teng->single('hoge_table',{
number => 25,
name => "tutugo"
});
在这种情况下,number和name被称为”数值”和”字符串”,但在Perl中并不相关。如果是用Golang来处理,则需要依赖interface{}。在这个例子中,st.Statement.Value成为interface{},这样我们可以通过它接收一个参数或者一个切片,并在此基础上构建查询的计划。
这次的实现方式不太顺畅,每次都要插入一个名为 st.Statement 的结构体,感觉有些不太自然…。
sql, args, err := dao.Select(model.TableNameHogeTable, model.HogeTableParam{
Number: st.Statement{Operation:"=", Value:25},
Name: st.Statement{Operation:"=", Value:"tutugo"},
}).SQL()
作为理想状况,可能是这种感觉吧(处理起来非常麻烦)。
sql, args, err := dao.Select(model.TableNameHogeTable, model.HogeTableParam{
Number: 25,
Name:"tutugo",
}).SQL()
零值处理
這個地方在Golang中是相當困難的部分。簡單來說
-
- 数値の0
-
- 空文字列 “”
boolean の false
在某些情况下,诸如零值的悲惨命运会被处理。例如,当使用json标签的omitempty选项时,该元素在进行json编码时会被忽略。在著名的gorm中,如果希望处理零值,推荐使用NullInt。
當情況變得複雜時很辛苦。
到目前为止,所有的代码都是这样的感觉。我一直介绍的都是简单的查询。
sql, args, err := dao.Select(model.TableNameHogeTable, model.HogeTableParam{
Number: st.Statement{Operation:"=", Value:25},
Name: st.Statement{Operation:"=", Value:"tutugo"},
}).SQL()
// => select * from hoge_table were number = 25 and name = "tutugo"
如果想在这里写一个例如10到30之间的数字,而且又不在20到25之间的数字,该怎么办呢…。
sql, args, err = dao.Select(model.TableNameHogeTable, &model.HogeTableParam{
ID: st.StatementAnd{
st.Statement{"between", []int{10, 30}},
st.Statement{"not between", []int{20, 25}},
},
}).SQL()
// => select * from hoge_table where
// ( ( id between ? and ? and id not between ? and ? ) )
// [10 30 20 25]
//
/ || ̄ ̄|| ∧_∧
|…..||__|| ( ^ω^ ) …?
| ̄ ̄\三⊂/ ̄ ̄ ̄/
| | ( ./ /
/ || ̄ ̄|| ∧_∧
|…..||__|| ( ^ω^ ) …?
| ̄ ̄\三⊂/ ̄ ̄ ̄/
| | ( ./ /
/ || ̄ ̄|| ∧_∧
|…..||__|| ( ^ω^ ) …?
| ̄ ̄\三⊂/ ̄ ̄ ̄/
| | ( ./ /
慢慢地我开始感到混乱了…尤其是一些让我尴尬的话语开始出现了。
課題點可以改善。
或者”或”不够有效。
可能是结构有问题的点。例如
select * from hoge_table
where ( id = 1 and name = "kuwahara")
or
( id = 0 and name = "nakai")
这样的查询目前还无法实现,这是因为之前不经思考就进行了这样的实施。
func dao.Select(tableName string, model.Param)
type Hoge struct{
ID int
Name string
Age int
}
type HogeParam struct{
ID StatementInterface
Name StatementInterface
Age StatementInterface
}
总之,就是无法创建 Param 的情况,对吧… 在创建查询时只能选择一个 model.param。我认为这些细节还有很多需要改进的地方。
无法加入
当事者が多くなることと時間の制約があることでしょう。
table_name.id のような表記にする必要がある
join hoge h on ごにょごにょ の「ごにょごにょ」を書けるようにする必要がある。
(おおよそは where 以下の部分と同じだし案外何とかなると思っている)
还有很长一段时间才能通过自制发电机满足一切需求吧…
5. 我的想法
-
- reflect すごい。
-
- 様々な言語で orm や クエリジェネレーター に関わっている方すごい。
- 落ち着けば仮完成させて放出してみたい(謎の自信)。
最后。参考链接
-
- https://metacpan.org/pod/Teng
-
- https://golang.org/pkg/reflect/
-
- http://gorm.io/ja_JP/docs/index.html
- https://github.com/jmoiron/sqlx