Merge pull request #126 from zhangyongding/master

support oracle
This commit is contained in:
Huan Du
2023-11-03 20:40:19 +08:00
committed by GitHub
13 changed files with 465 additions and 25 deletions
+2
View File
@@ -247,6 +247,8 @@ func (args *Args) compileArg(buf *stringBuilder, flavor Flavor, values []interfa
fmt.Fprintf(buf, "$%d", len(values)+1)
case SQLServer:
fmt.Fprintf(buf, "@p%d", len(values)+1)
case Oracle:
fmt.Fprintf(buf, ":%d", len(values)+1)
default:
panic(fmt.Errorf("Args.CompileWithFlavor: invalid flavor %v (%v)", flavor, int(flavor)))
}
+16
View File
@@ -1,6 +1,8 @@
package sqlbuilder
import (
"reflect"
"github.com/huandu/xstrings"
)
@@ -13,8 +15,19 @@ var (
//
// Field mapper is called only once on a Struct when the Struct is used to create builder for the first time.
DefaultFieldMapper FieldMapperFunc
// DefaultGetAlias is the default alias and dbtag get func
DefaultGetAlias GetAliasFunc
)
func init() {
DefaultGetAlias = func(field *reflect.StructField) (alias string, dbtag string) {
dbtag = field.Tag.Get(DBTag)
alias = dbtag
return
}
}
// FieldMapperFunc is a func to map struct field names to column names,
// which will be used in query as columns.
type FieldMapperFunc func(name string) string
@@ -28,3 +41,6 @@ type FieldMapperFunc func(name string) string
func SnakeCaseMapper(field string) string {
return xstrings.ToSnakeCase(field)
}
// GetAliasFunc is a func to get alias and dbtag
type GetAliasFunc func(field *reflect.StructField) (alias string, dbtag string)
+7 -2
View File
@@ -19,6 +19,7 @@ const (
CQL
ClickHouse
Presto
Oracle
)
var (
@@ -58,6 +59,8 @@ func (f Flavor) String() string {
return "ClickHouse"
case Presto:
return "Presto"
case Oracle:
return "Oracle"
}
return "<invalid>"
@@ -84,6 +87,8 @@ func (f Flavor) Interpolate(sql string, args []interface{}) (string, error) {
return clickhouseInterpolate(sql, args...)
case Presto:
return prestoInterpolate(sql, args...)
case Oracle:
return oracleInterpolate(sql, args...)
}
return "", ErrInterpolateNotImplemented
@@ -140,7 +145,7 @@ func (f Flavor) Quote(name string) string {
switch f {
case MySQL, ClickHouse:
return fmt.Sprintf("`%s`", name)
case PostgreSQL, SQLServer, SQLite, Presto:
case PostgreSQL, SQLServer, SQLite, Presto, Oracle:
return fmt.Sprintf(`"%s"`, name)
case CQL:
return fmt.Sprintf("'%s'", name)
@@ -152,7 +157,7 @@ func (f Flavor) Quote(name string) string {
// PrepareInsertIgnore prepares the insert builder to build insert ignore SQL statement based on the sql flavor
func (f Flavor) PrepareInsertIgnore(table string, ib *InsertBuilder) {
switch ib.args.Flavor {
case MySQL:
case MySQL, Oracle:
ib.verb = "INSERT IGNORE"
case PostgreSQL:
+18
View File
@@ -138,3 +138,21 @@ func ExampleFlavor_Interpolate_cql() {
// SELECT name FROM user WHERE id = 1234 AND name = 'Charmy Liu'
// <nil>
}
func ExampleFlavor_Interpolate_oracle() {
sb := Oracle.NewSelectBuilder()
sb.Select("name").From("user").Where(
sb.E("id", 1234),
sb.E("name", "Charmy Liu"),
sb.E("enabled", true),
)
sql, args := sb.Build()
query, err := Oracle.Interpolate(sql, args)
fmt.Println(query)
fmt.Println(err)
// Output:
// SELECT name FROM user WHERE id = 1234 AND name = 'Charmy Liu' AND enabled = 1
// <nil>
}
+31
View File
@@ -120,6 +120,37 @@ func (ib *InsertBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
buf := newStringBuilder()
ib.injection.WriteTo(buf, insertMarkerInit)
if len(ib.values) > 1 && ib.args.Flavor == Oracle {
buf.WriteLeadingString(ib.verb)
buf.WriteString(" ALL")
for _, v := range ib.values {
if len(ib.table) > 0 {
buf.WriteString(" INTO ")
buf.WriteString(ib.table)
}
ib.injection.WriteTo(buf, insertMarkerAfterInsertInto)
if len(ib.cols) > 0 {
buf.WriteLeadingString("(")
buf.WriteString(strings.Join(ib.cols, ", "))
buf.WriteString(")")
ib.injection.WriteTo(buf, insertMarkerAfterCols)
}
buf.WriteLeadingString("VALUES ")
values := make([]string, 0, len(ib.values))
values = append(values, fmt.Sprintf("(%v)", strings.Join(v, ", ")))
buf.WriteString(strings.Join(values, ", "))
}
buf.WriteString(" SELECT 1 from DUAL")
ib.injection.WriteTo(buf, insertMarkerAfterValues)
return ib.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}
if len(ib.table) > 0 {
buf.WriteLeadingString(ib.verb)
buf.WriteString(" INTO ")
+16
View File
@@ -65,6 +65,22 @@ func ExampleInsertBuilder() {
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
}
func ExampleInsertBuilder_Oracle() {
ib := Oracle.NewInsertBuilder()
ib.InsertInto("demo.user")
ib.Cols("id", "name", "status")
ib.Values(1, "Huan Du", 1)
ib.Values(2, "Charmy Liu", 1)
sql, args := ib.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// INSERT ALL INTO demo.user (id, name, status) VALUES (:1, :2, :3) INTO demo.user (id, name, status) VALUES (:4, :5, :6) SELECT 1 from DUAL
// [1 Huan Du 1 2 Charmy Liu 1]
}
func ExampleInsertBuilder_insertIgnore() {
ib := NewInsertBuilder()
ib.InsertIgnoreInto("demo.user")
+195 -14
View File
@@ -399,6 +399,170 @@ func prestoInterpolate(query string, args ...interface{}) (string, error) {
return mysqlLikeInterpolate(Presto, query, args...)
}
// oraclelInterpolate parses query and replace all ":*" with encoded args.
// If there are more ":*" than len(args), returns ErrMissingArgs.
// Otherwise, if there are less ":*" than len(args), the redundant args are omitted.
func oracleInterpolate(query string, args ...interface{}) (string, error) {
// Roughly estimate the size to avoid useless memory allocation and copy.
buf := make([]byte, 0, len(query)+len(args)*20)
var quote rune
var dollarQuote string
var err error
var idx int64
max := len(args)
escaping := false
offset := 0
target := query
r, sz := utf8.DecodeRuneInString(target)
for ; sz != 0; r, sz = utf8.DecodeRuneInString(target) {
offset += sz
target = query[offset:]
if escaping {
escaping = false
continue
}
switch r {
case ':':
if quote != 0 {
if quote != ':' {
continue
}
// Try to find the end of dollar quote.
pos := offset
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && r != ':'; r, sz = utf8.DecodeRuneInString(target) {
pos += sz
target = query[pos:]
}
if sz == 0 {
break
}
if r == ':' {
dq := query[offset : pos+sz]
offset = pos
target = query[offset:]
if dq == dollarQuote {
quote = 0
dollarQuote = ""
offset += sz
target = query[offset:]
}
continue
}
continue
}
oldSz := sz
pos := offset
r, sz = utf8.DecodeRuneInString(target)
if '1' <= r && r <= '9' {
// A placeholder is found.
pos += sz
target = query[pos:]
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && '0' <= r && r <= '9'; r, sz = utf8.DecodeRuneInString(target) {
pos += sz
target = query[pos:]
}
idx, err = strconv.ParseInt(query[offset:pos], 10, strconv.IntSize)
if err != nil {
return "", err
}
if int(idx) >= max+1 {
return "", ErrInterpolateMissingArgs
}
buf = append(buf, query[:offset-oldSz]...)
buf, err = encodeValue(buf, args[idx-1], Oracle)
if err != nil {
return "", err
}
query = target
offset = 0
if sz == 0 {
break
}
continue
}
// Try to find the beginning of dollar quote.
for ; sz != 0 && r != ':' && unicode.IsLetter(r); r, sz = utf8.DecodeRuneInString(target) {
pos += sz
target = query[pos:]
}
if sz == 0 {
break
}
if !unicode.IsLetter(r) && r != ':' {
continue
}
pos += sz
quote = ':'
dollarQuote = query[offset:pos]
offset = pos
target = query[offset:]
case '\'':
if quote == '\'' {
// PostgreSQL uses two single quotes to represent one single quote.
r, sz = utf8.DecodeRuneInString(target)
if r == '\'' {
offset += sz
target = query[offset:]
continue
}
quote = 0
continue
}
if quote == 0 {
quote = '\''
}
case '"':
if quote == '"' {
quote = 0
continue
}
if quote == 0 {
quote = '"'
}
case '\\':
if quote == '\'' || quote == '"' {
escaping = true
}
}
}
buf = append(buf, query...)
return *(*string)(unsafe.Pointer(&buf)), nil
}
func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
switch v := arg.(type) {
case nil:
@@ -420,32 +584,35 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
// In SQL standard, the precision of fractional seconds in time literal is up to 6 digits.
// Round up v.
v = v.Add(500 * time.Nanosecond)
buf = append(buf, '\'')
switch flavor {
case MySQL:
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999'")...)
case PostgreSQL:
buf = append(buf, v.Format("2006-01-02 15:04:05.999999 MST")...)
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999 MST'")...)
case SQLite:
buf = append(buf, v.Format("2006-01-02 15:04:05.000")...)
buf = append(buf, v.Format("'2006-01-02 15:04:05.000'")...)
case SQLServer:
buf = append(buf, v.Format("2006-01-02 15:04:05.999999 Z07:00")...)
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999 Z07:00'")...)
case CQL:
buf = append(buf, v.Format("2006-01-02 15:04:05.999999Z0700")...)
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999Z0700'")...)
case ClickHouse:
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999'")...)
case Presto:
buf = append(buf, v.Format("2006-01-02 15:04:05.000")...)
}
buf = append(buf, v.Format("'2006-01-02 15:04:05.000'")...)
buf = append(buf, '\'')
case Oracle:
buf = append(buf, "to_timestamp('"...)
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
buf = append(buf, "', 'YYYY-MM-DD HH24:MI:SS.FF')"...)
}
case fmt.Stringer:
buf = quoteStringValue(buf, v.String(), flavor)
@@ -455,10 +622,19 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
switch k := primative.Kind(); k {
case reflect.Bool:
if primative.Bool() {
buf = append(buf, "TRUE"...)
} else {
buf = append(buf, "FALSE"...)
switch flavor {
case Oracle:
if primative.Bool() {
buf = append(buf, '1')
} else {
buf = append(buf, '0')
}
default:
if primative.Bool() {
buf = append(buf, "TRUE"...)
} else {
buf = append(buf, "FALSE"...)
}
}
case reflect.Int:
@@ -553,6 +729,11 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
buf = append(buf, "from_hex('"...)
buf = appendHex(buf, data)
buf = append(buf, "')"...)
case Oracle:
buf = append(buf, "hextoraw('"...)
buf = appendHex(buf, data)
buf = append(buf, "')"...)
}
default:
+41
View File
@@ -283,6 +283,47 @@ func TestFlavorInterpolate(t *testing.T) {
"SELECT ?", []interface{}{errorValuer(1)},
"", ErrErrorValuer,
},
{
Oracle,
"SELECT * FROM a WHERE name = :3 AND state IN (:2, :4, :1, :6, :5)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
"SELECT * FROM a WHERE name = 8 AND state IN (42, -16, 'I\\'m fine', 64, 32)", nil,
},
{
Oracle,
"SELECT * FROM :abc::1:abc:1:1 WHERE name = \":1\" AND state IN (:2, ':1', :3, :6, :5, :4, :2) :3", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
"SELECT * FROM :abc::1:abc:1'\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'' WHERE name = \":1\" AND state IN (42, ':1', 8, 64, 32, 16, 42) 8", nil,
},
{
Oracle,
"SELECT :1, :2, :3, :4, :5, :6, :7, :8, :9, :11, :a", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil, 10, 11, 12},
"SELECT 1, 0, 1.234567, 9.87654321, NULL, hextoraw('49276D206279746573'), to_timestamp('2019-04-24 12:23:34.123457', 'YYYY-MM-DD HH24:MI:SS.FF'), '0000-00-00', NULL, 11, :a", nil,
},
{
Oracle,
"SELECT '\\':1', \"\\\":1\", `:1`, \\:1a, ::1::, :a :b: :a : :1:b:1:1 :a: :", []interface{}{Oracle},
"SELECT '\\':1', \"\\\":1\", `'Oracle'`, \\'Oracle'a, ::1::, :a :b: :a : :1:b:1'Oracle' :a: :", nil,
},
{
Oracle,
"SELECT * FROM a WHERE name = 'Huan''Du'':1' AND desc = :1", []interface{}{"c'mon"},
"SELECT * FROM a WHERE name = 'Huan''Du'':1' AND desc = 'c\\'mon'", nil,
},
{
Oracle,
"SELECT :1", nil,
"", ErrInterpolateMissingArgs,
},
{
Oracle,
"SELECT :1", []interface{}{complex(1, 2)},
"", ErrInterpolateUnsupportedArgs,
},
{
Oracle,
"SELECT :12345678901234567890", nil,
"", errOutOfRange,
},
}
for idx, c := range cases {
+65 -1
View File
@@ -237,6 +237,8 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
buf := newStringBuilder()
sb.injection.WriteTo(buf, selectMarkerInit)
oraclePage := flavor == Oracle && (sb.limit >= 0 || sb.offset >= 0)
if len(sb.selectCols) > 0 {
buf.WriteLeadingString("SELECT ")
@@ -244,11 +246,49 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
buf.WriteString("DISTINCT ")
}
buf.WriteString(strings.Join(sb.selectCols, ", "))
if oraclePage {
var selectCols = make([]string, 0, len(sb.selectCols))
for i := range sb.selectCols {
cols := strings.SplitN(sb.selectCols[i], ".", 2)
if len(cols) == 1 {
selectCols = append(selectCols, cols[0])
} else {
selectCols = append(selectCols, cols[1])
}
}
buf.WriteString(strings.Join(selectCols, ", "))
} else {
buf.WriteString(strings.Join(sb.selectCols, ", "))
}
}
sb.injection.WriteTo(buf, selectMarkerAfterSelect)
if oraclePage {
if len(sb.selectCols) > 0 {
buf.WriteLeadingString("FROM ( SELECT ")
if sb.distinct {
buf.WriteString("DISTINCT ")
}
var selectCols = make([]string, 0, len(sb.selectCols)+1)
selectCols = append(selectCols, "ROWNUM r")
for i := range sb.selectCols {
cols := strings.SplitN(sb.selectCols[i], ".", 2)
if len(cols) == 1 {
selectCols = append(selectCols, cols[0])
} else {
selectCols = append(selectCols, cols[1])
}
}
buf.WriteString(strings.Join(selectCols, ", "))
buf.WriteLeadingString("FROM ( SELECT ")
buf.WriteString(strings.Join(sb.selectCols, ", "))
}
}
if len(sb.tables) > 0 {
buf.WriteLeadingString("FROM ")
buf.WriteString(strings.Join(sb.tables, ", "))
@@ -354,6 +394,30 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
buf.WriteString(strconv.Itoa(sb.limit))
buf.WriteString(" ROWS ONLY")
}
case Oracle:
if oraclePage {
buf.WriteString(" ) ")
if len(sb.tables) > 0 {
buf.WriteString(strings.Join(sb.tables, ", "))
}
min := sb.offset
if min < 0 {
min = 0
}
buf.WriteString(" ) WHERE ")
if sb.limit >= 0 {
buf.WriteString("r BETWEEN ")
buf.WriteString(strconv.Itoa(min + 1))
buf.WriteString(" AND ")
buf.WriteString(strconv.Itoa(sb.limit + min))
} else {
buf.WriteString("r >= ")
buf.WriteString(strconv.Itoa(min + 1))
}
}
}
if sb.limit >= 0 {
+25 -1
View File
@@ -112,7 +112,7 @@ func ExampleSelectBuilder_join() {
}
func ExampleSelectBuilder_limit_offset() {
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, CQL, ClickHouse, Presto}
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, CQL, ClickHouse, Presto, Oracle}
results := make([][]string, len(flavors))
sb := NewSelectBuilder()
saveResults := func() {
@@ -138,6 +138,7 @@ func ExampleSelectBuilder_limit_offset() {
// PostgreSQL: Offset can be set without limit.
// SQLServer: Offset can be set without limit.
// CQL: Ignore offset.
// Oracle: Offset can be set without limit.
sb.Limit(-1)
sb.Offset(0)
saveResults()
@@ -157,6 +158,15 @@ func ExampleSelectBuilder_limit_offset() {
sb.Offset(-1)
saveResults()
// Case #5: limit >= 0 and offset >= 0 order by id
//
// CQL: Ignore offset.
// All others: Set both limit and offset.
sb.Limit(1)
sb.Offset(1)
sb.OrderBy("id")
saveResults()
for i, result := range results {
fmt.Println()
fmt.Println(flavors[i])
@@ -173,42 +183,56 @@ func ExampleSelectBuilder_limit_offset() {
// #2: SELECT * FROM user
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
// #4: SELECT * FROM user LIMIT 1
// #5: SELECT * FROM user ORDER BY id LIMIT 1 OFFSET 1
//
// PostgreSQL
// #1: SELECT * FROM user
// #2: SELECT * FROM user OFFSET 0
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
// #4: SELECT * FROM user LIMIT 1
// #5: SELECT * FROM user ORDER BY id LIMIT 1 OFFSET 1
//
// SQLite
// #1: SELECT * FROM user
// #2: SELECT * FROM user
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
// #4: SELECT * FROM user LIMIT 1
// #5: SELECT * FROM user ORDER BY id LIMIT 1 OFFSET 1
//
// SQLServer
// #1: SELECT * FROM user
// #2: SELECT * FROM user ORDER BY 1 OFFSET 0 ROWS
// #3: SELECT * FROM user ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
// #4: SELECT * FROM user ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
// #5: SELECT * FROM user ORDER BY id OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY
//
// CQL
// #1: SELECT * FROM user
// #2: SELECT * FROM user
// #3: SELECT * FROM user LIMIT 1
// #4: SELECT * FROM user LIMIT 1
// #5: SELECT * FROM user ORDER BY id LIMIT 1
//
// ClickHouse
// #1: SELECT * FROM user
// #2: SELECT * FROM user
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
// #4: SELECT * FROM user LIMIT 1
// #5: SELECT * FROM user ORDER BY id LIMIT 1 OFFSET 1
//
// Presto
// #1: SELECT * FROM user
// #2: SELECT * FROM user OFFSET 0
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
// #4: SELECT * FROM user LIMIT 1
// #5: SELECT * FROM user ORDER BY id LIMIT 1 OFFSET 1
//
// Oracle
// #1: SELECT * FROM user
// #2: SELECT * FROM ( SELECT ROWNUM r, * FROM ( SELECT * FROM user ) user ) WHERE r >= 1
// #3: SELECT * FROM ( SELECT ROWNUM r, * FROM ( SELECT * FROM user ) user ) WHERE r BETWEEN 1 AND 1
// #4: SELECT * FROM ( SELECT ROWNUM r, * FROM ( SELECT * FROM user ) user ) WHERE r BETWEEN 1 AND 1
// #5: SELECT * FROM ( SELECT ROWNUM r, * FROM ( SELECT * FROM user ORDER BY id ) user ) WHERE r BETWEEN 2 AND 2
}
func ExampleSelectBuilder_ForUpdate() {
+34 -1
View File
@@ -9,6 +9,7 @@ import (
"reflect"
"regexp"
"sort"
"strings"
)
var (
@@ -308,7 +309,7 @@ func (s *Struct) selectFromWithTags(table string, with, without []string) (sb *S
cols := make([]string, 0, len(tagged.ForRead))
for _, sf := range tagged.ForRead {
if s.Flavor != CQL {
if s.Flavor != CQL && !strings.ContainsRune(sf.Alias, '.') {
buf.WriteString(table)
buf.WriteRune('.')
}
@@ -704,6 +705,38 @@ func (s *Struct) valuesWithTags(with, without []string, value interface{}) (valu
return
}
// ForeachRead foreach tags.
func (s *Struct) ForeachRead(trans func(dbtag string, isQuoted bool, field reflect.StructField)) {
s.foreachReadWithTags(s.withTags, s.withoutTags, trans)
}
func (s *Struct) foreachReadWithTags(with, without []string, trans func(dbtag string, isQuoted bool, field reflect.StructField)) {
sfs := s.structFieldsParser()
tagged := sfs.FilterTags(with, without)
if tagged == nil {
return
}
for _, sf := range tagged.ForRead {
trans(sf.DBTag, sf.IsQuoted, sf.Field)
}
}
// ForeachWrite foreach tags.
func (s *Struct) ForeachWrite(trans func(dbtag string, isQuoted bool, field reflect.StructField)) {
s.foreachWriteWithTags(s.withTags, s.withoutTags, trans)
}
func (s *Struct) foreachWriteWithTags(with, without []string, trans func(dbtag string, isQuoted bool, field reflect.StructField)) {
sfs := s.structFieldsParser()
tagged := sfs.FilterTags(with, without)
if tagged == nil {
return
}
for _, sf := range tagged.ForWrite {
trans(sf.DBTag, sf.IsQuoted, sf.Field)
}
}
func dereferencedType(t reflect.Type) reflect.Type {
for k := t.Kind(); k == reflect.Ptr || k == reflect.Interface; k = t.Kind() {
t = t.Elem()
+8
View File
@@ -6,6 +6,7 @@ package sqlbuilder
import (
"database/sql/driver"
"fmt"
"reflect"
"testing"
"time"
@@ -302,6 +303,13 @@ func TestWithAndWithoutTags(t *testing.T) {
a.Equal(structTags.WithoutTag("tag3").WithTag("tag1").WithTag("tag3", "tag2").WithoutTag("tag1", "", "tag3").Columns(), []string{"b"})
}
func TestStructForeachRead(t *testing.T) {
// a := assert.New(t)
userForTest.ForeachRead(func(dbtag string, isQuoted bool, field reflect.StructField) {
t.Logf("%s\n", dbtag)
})
}
type State int
type testDB int
type testRows int
+7 -6
View File
@@ -28,6 +28,8 @@ type structField struct {
As string
Tags []string
IsQuoted bool
DBTag string
Field reflect.StructField
omitEmptyTags omitEmptyTagMap
}
@@ -85,17 +87,14 @@ func (sfs *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix st
}
// Parse DBTag.
dbtag := field.Tag.Get(DBTag)
alias, dbtag := DefaultGetAlias(&field)
if dbtag == "-" {
if alias == "-" {
continue
}
alias := dbtag
if dbtag == "" {
if alias == "" {
alias = field.Name
if mapper != nil {
alias = mapper(alias)
}
@@ -137,6 +136,8 @@ func (sfs *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix st
As: fieldas,
Tags: tags,
IsQuoted: isQuoted,
DBTag: dbtag,
Field: field,
omitEmptyTags: omitEmptyTags,
}