mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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 ")
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user