From f9a6990ecb22a83eebd359944453dead0b72a8c5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 31 May 2022 11:00:28 +0800 Subject: [PATCH 01/25] Refactor orderby and support arguments (#2150) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2150 --- dialects/mysql.go | 2 +- engine.go | 12 +- go.mod | 2 +- go.sum | 4 +- integrations/session_find_test.go | 4 + interface.go | 2 +- internal/statements/cond.go | 111 +++++++++ internal/statements/join.go | 78 +++++++ internal/statements/order_by.go | 90 ++++++++ internal/statements/query.go | 302 +++++++++++++------------ internal/statements/select.go | 137 +++++++++++ internal/statements/statement.go | 362 ++---------------------------- internal/statements/table_name.go | 56 +++++ internal/utils/builder.go | 27 +++ session.go | 4 +- session_delete.go | 133 +++++------ session_find.go | 22 +- session_update.go | 97 ++++---- 18 files changed, 813 insertions(+), 632 deletions(-) create mode 100644 internal/statements/cond.go create mode 100644 internal/statements/join.go create mode 100644 internal/statements/order_by.go create mode 100644 internal/statements/select.go create mode 100644 internal/statements/table_name.go create mode 100644 internal/utils/builder.go diff --git a/dialects/mysql.go b/dialects/mysql.go index 82df04dd..31e7b788 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -400,7 +400,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName " `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, `CHARACTER_MAXIMUM_LENGTH`, " + alreadyQuoted + " AS NEEDS_QUOTE " + "FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" + - " ORDER BY `COLUMNS`.ORDINAL_POSITION" + " ORDER BY `COLUMNS`.ORDINAL_POSITION ASC" rows, err := queryer.QueryContext(ctx, s, args...) if err != nil { diff --git a/engine.go b/engine.go index b7dcf5a2..81cfc7a9 100644 --- a/engine.go +++ b/engine.go @@ -380,7 +380,7 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error { seq = 0 } } - var colName = strings.Trim(parts[0], `"`) + colName := strings.Trim(parts[0], `"`) if col := table.GetColumn(colName); col != nil { col.Indexes[index.Name] = index.Type } else { @@ -502,9 +502,9 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w } } - var dstTableName = dstTable.Name - var quoter = dstDialect.Quoter().Quote - var quotedDstTableName = quoter(dstTable.Name) + dstTableName := dstTable.Name + quoter := dstDialect.Quoter().Quote + quotedDstTableName := quoter(dstTable.Name) if dstDialect.URI().Schema != "" { dstTableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, dstTable.Name) quotedDstTableName = fmt.Sprintf("%s.%s", quoter(dstDialect.URI().Schema), quoter(dstTable.Name)) @@ -1006,10 +1006,10 @@ func (engine *Engine) Asc(colNames ...string) *Session { } // OrderBy will generate "ORDER BY order" -func (engine *Engine) OrderBy(order string) *Session { +func (engine *Engine) OrderBy(order interface{}, args ...interface{}) *Session { session := engine.NewSession() session.isAutoClose = true - return session.OrderBy(order) + return session.OrderBy(order, args...) } // Prepare enables prepare statement diff --git a/go.mod b/go.mod index 0764d73a..7bde41ae 100644 --- a/go.mod +++ b/go.mod @@ -17,5 +17,5 @@ require ( github.com/syndtr/goleveldb v1.0.0 github.com/ziutek/mymysql v1.5.4 modernc.org/sqlite v1.14.2 - xorm.io/builder v0.3.9 + xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 ) diff --git a/go.sum b/go.sum index 8e7ac44b..8bdc9798 100644 --- a/go.sum +++ b/go.sum @@ -659,5 +659,5 @@ modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao= modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= -xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= -xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 7f42d096..6701b1b5 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -247,6 +247,10 @@ func TestOrder(t *testing.T) { users2 := make([]Userinfo, 0) err = testEngine.Asc("id", "username").Desc("height").Find(&users2) assert.NoError(t, err) + + users = make([]Userinfo, 0) + err = testEngine.OrderBy("CASE WHEN username LIKE ? THEN 0 ELSE 1 END DESC", "a").Find(&users) + assert.NoError(t, err) } func TestGroupBy(t *testing.T) { diff --git a/interface.go b/interface.go index b9e88505..55ffebe4 100644 --- a/interface.go +++ b/interface.go @@ -54,7 +54,7 @@ type Interface interface { Nullable(...string) *Session Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session Omit(columns ...string) *Session - OrderBy(order string) *Session + OrderBy(order interface{}, args ...interface{}) *Session Ping() error Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) diff --git a/internal/statements/cond.go b/internal/statements/cond.go new file mode 100644 index 00000000..dfc6c208 --- /dev/null +++ b/internal/statements/cond.go @@ -0,0 +1,111 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +type QuoteReplacer struct { + *builder.BytesWriter + quoter schemas.Quoter +} + +func (q *QuoteReplacer) Write(p []byte) (n int, err error) { + c := q.quoter.Replace(string(p)) + return q.BytesWriter.Builder.WriteString(c) +} + +func (statement *Statement) QuoteReplacer(w *builder.BytesWriter) *QuoteReplacer { + return &QuoteReplacer{ + BytesWriter: w, + quoter: statement.dialect.Quoter(), + } +} + +// Where add Where statement +func (statement *Statement) Where(query interface{}, args ...interface{}) *Statement { + return statement.And(query, args...) +} + +// And add Where & and statement +func (statement *Statement) And(query interface{}, args ...interface{}) *Statement { + switch qr := query.(type) { + case string: + cond := builder.Expr(qr, args...) + statement.cond = statement.cond.And(cond) + case map[string]interface{}: + cond := make(builder.Eq) + for k, v := range qr { + cond[statement.quote(k)] = v + } + statement.cond = statement.cond.And(cond) + case builder.Cond: + statement.cond = statement.cond.And(qr) + for _, v := range args { + if vv, ok := v.(builder.Cond); ok { + statement.cond = statement.cond.And(vv) + } + } + default: + statement.LastError = ErrConditionType + } + + return statement +} + +// Or add Where & Or statement +func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement { + switch qr := query.(type) { + case string: + cond := builder.Expr(qr, args...) + statement.cond = statement.cond.Or(cond) + case map[string]interface{}: + cond := make(builder.Eq) + for k, v := range qr { + cond[statement.quote(k)] = v + } + statement.cond = statement.cond.Or(cond) + case builder.Cond: + statement.cond = statement.cond.Or(qr) + for _, v := range args { + if vv, ok := v.(builder.Cond); ok { + statement.cond = statement.cond.Or(vv) + } + } + default: + statement.LastError = ErrConditionType + } + return statement +} + +// In generate "Where column IN (?) " statement +func (statement *Statement) In(column string, args ...interface{}) *Statement { + in := builder.In(statement.quote(column), args...) + statement.cond = statement.cond.And(in) + return statement +} + +// NotIn generate "Where column NOT IN (?) " statement +func (statement *Statement) NotIn(column string, args ...interface{}) *Statement { + notIn := builder.NotIn(statement.quote(column), args...) + statement.cond = statement.cond.And(notIn) + return statement +} + +// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function +func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement { + statement.NoAutoCondition = true + if len(no) > 0 { + statement.NoAutoCondition = no[0] + } + return statement +} + +// Conds returns condtions +func (statement *Statement) Conds() builder.Cond { + return statement.cond +} diff --git a/internal/statements/join.go b/internal/statements/join.go new file mode 100644 index 00000000..45fc2441 --- /dev/null +++ b/internal/statements/join.go @@ -0,0 +1,78 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/builder" + "xorm.io/xorm/dialects" + "xorm.io/xorm/internal/utils" + "xorm.io/xorm/schemas" +) + +// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN +func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement { + var buf strings.Builder + if len(statement.JoinStr) > 0 { + fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) + } else { + fmt.Fprintf(&buf, "%v JOIN ", joinOP) + } + + switch tp := tablename.(type) { + case builder.Builder: + subSQL, subQueryArgs, err := tp.ToSQL() + if err != nil { + statement.LastError = err + return statement + } + + fields := strings.Split(tp.TableName(), ".") + aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) + aliasName = schemas.CommonQuoter.Trim(aliasName) + + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) + statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + case *builder.Builder: + subSQL, subQueryArgs, err := tp.ToSQL() + if err != nil { + statement.LastError = err + return statement + } + + fields := strings.Split(tp.TableName(), ".") + aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) + aliasName = schemas.CommonQuoter.Trim(aliasName) + + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) + statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + default: + tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) + if !utils.IsSubQuery(tbName) { + var buf strings.Builder + _ = statement.dialect.Quoter().QuoteTo(&buf, tbName) + tbName = buf.String() + } else { + tbName = statement.ReplaceQuote(tbName) + } + fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) + } + + statement.JoinStr = buf.String() + statement.joinArgs = append(statement.joinArgs, args...) + return statement +} + +func (statement *Statement) writeJoin(w builder.Writer) error { + if statement.JoinStr != "" { + if _, err := fmt.Fprint(w, " ", statement.JoinStr); err != nil { + return err + } + w.Append(statement.joinArgs...) + } + return nil +} diff --git a/internal/statements/order_by.go b/internal/statements/order_by.go new file mode 100644 index 00000000..08a8263b --- /dev/null +++ b/internal/statements/order_by.go @@ -0,0 +1,90 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/builder" +) + +func (statement *Statement) HasOrderBy() bool { + return statement.orderStr != "" +} + +// ResetOrderBy reset ordery conditions +func (statement *Statement) ResetOrderBy() { + statement.orderStr = "" + statement.orderArgs = nil +} + +// WriteOrderBy write order by to writer +func (statement *Statement) WriteOrderBy(w builder.Writer) error { + if len(statement.orderStr) > 0 { + if _, err := fmt.Fprintf(w, " ORDER BY %s", statement.orderStr); err != nil { + return err + } + w.Append(statement.orderArgs...) + } + return nil +} + +// OrderBy generate "Order By order" statement +func (statement *Statement) OrderBy(order interface{}, args ...interface{}) *Statement { + if len(statement.orderStr) > 0 { + statement.orderStr += ", " + } + var rawOrder string + switch t := order.(type) { + case (*builder.Expression): + rawOrder = t.Content() + args = t.Args() + case string: + rawOrder = t + default: + statement.LastError = ErrUnSupportedSQLType + return statement + } + statement.orderStr += statement.ReplaceQuote(rawOrder) + if len(args) > 0 { + statement.orderArgs = append(statement.orderArgs, args...) + } + return statement +} + +// Desc generate `ORDER BY xx DESC` +func (statement *Statement) Desc(colNames ...string) *Statement { + var buf strings.Builder + if len(statement.orderStr) > 0 { + fmt.Fprint(&buf, statement.orderStr, ", ") + } + for i, col := range colNames { + if i > 0 { + fmt.Fprint(&buf, ", ") + } + _ = statement.dialect.Quoter().QuoteTo(&buf, col) + fmt.Fprint(&buf, " DESC") + } + statement.orderStr = buf.String() + return statement +} + +// Asc provide asc order by query condition, the input parameters are columns. +func (statement *Statement) Asc(colNames ...string) *Statement { + var buf strings.Builder + if len(statement.orderStr) > 0 { + fmt.Fprint(&buf, statement.orderStr, ", ") + } + for i, col := range colNames { + if i > 0 { + fmt.Fprint(&buf, ", ") + } + _ = statement.dialect.Quoter().QuoteTo(&buf, col) + fmt.Fprint(&buf, " ASC") + } + statement.orderStr = buf.String() + return statement +} diff --git a/internal/statements/query.go b/internal/statements/query.go index 8b383866..f72c8602 100644 --- a/internal/statements/query.go +++ b/internal/statements/query.go @@ -11,6 +11,7 @@ import ( "strings" "xorm.io/builder" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -28,7 +29,7 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int return "", nil, ErrTableNotFound } - var columnStr = statement.ColumnStr() + columnStr := statement.ColumnStr() if len(statement.SelectStr) > 0 { columnStr = statement.SelectStr } else { @@ -58,19 +59,7 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int return "", nil, err } - sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) - if err != nil { - return "", nil, err - } - args := append(statement.joinArgs, condArgs...) - - // for mssql and use limit - qs := strings.Count(sqlStr, "?") - if len(args)*2 == qs { - args = append(args, args...) - } - - return sqlStr, args, nil + return statement.genSelectSQL(columnStr, true, true) } // GenSumSQL generates sum SQL @@ -83,7 +72,7 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri return "", nil, err } - var sumStrs = make([]string, 0, len(columns)) + sumStrs := make([]string, 0, len(columns)) for _, colName := range columns { if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") { colName = statement.quote(colName) @@ -94,16 +83,11 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri } sumSelect := strings.Join(sumStrs, ", ") - if err := statement.mergeConds(bean); err != nil { + if err := statement.MergeConds(bean); err != nil { return "", nil, err } - sqlStr, condArgs, err := statement.genSelectSQL(sumSelect, true, true) - if err != nil { - return "", nil, err - } - - return sqlStr, append(statement.joinArgs, condArgs...), nil + return statement.genSelectSQL(sumSelect, true, true) } // GenGetSQL generates Get SQL @@ -119,7 +103,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, } } - var columnStr = statement.ColumnStr() + columnStr := statement.ColumnStr() if len(statement.SelectStr) > 0 { columnStr = statement.SelectStr } else { @@ -146,7 +130,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, } if isStruct { - if err := statement.mergeConds(bean); err != nil { + if err := statement.MergeConds(bean); err != nil { return "", nil, err } } else { @@ -155,12 +139,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{}, } } - sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) - if err != nil { - return "", nil, err - } - - return sqlStr, append(statement.joinArgs, condArgs...), nil + return statement.genSelectSQL(columnStr, true, true) } // GenCountSQL generates the SQL for counting @@ -175,12 +154,12 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa if err := statement.SetRefBean(beans[0]); err != nil { return "", nil, err } - if err := statement.mergeConds(beans[0]); err != nil { + if err := statement.MergeConds(beans[0]); err != nil { return "", nil, err } } - var selectSQL = statement.SelectStr + selectSQL := statement.SelectStr if len(selectSQL) <= 0 { if statement.IsDistinct { selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr()) @@ -206,55 +185,58 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa sqlStr = fmt.Sprintf("SELECT %s FROM (%s) sub", selectSQL, sqlStr) } - return sqlStr, append(statement.joinArgs, condArgs...), nil + return sqlStr, condArgs, nil } -func (statement *Statement) fromBuilder() *strings.Builder { - var builder strings.Builder - var quote = statement.quote - var dialect = statement.dialect - - builder.WriteString(" FROM ") - - if dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { - builder.WriteString(statement.TableName()) - } else { - builder.WriteString(quote(statement.TableName())) +func (statement *Statement) writeFrom(w builder.Writer) error { + if _, err := fmt.Fprint(w, " FROM "); err != nil { + return err } + if err := statement.writeTableName(w); err != nil { + return err + } + if err := statement.writeAlias(w); err != nil { + return err + } + return statement.writeJoin(w) +} - if statement.TableAlias != "" { - if dialect.URI().DBType == schemas.ORACLE { - builder.WriteString(" ") - } else { - builder.WriteString(" AS ") +func (statement *Statement) writeLimitOffset(w builder.Writer) error { + if statement.Start > 0 { + if statement.LimitN != nil { + _, err := fmt.Fprintf(w, " LIMIT %v OFFSET %v", *statement.LimitN, statement.Start) + return err } - builder.WriteString(quote(statement.TableAlias)) + _, err := fmt.Fprintf(w, " LIMIT 0 OFFSET %v", statement.Start) + return err } - if statement.JoinStr != "" { - builder.WriteString(" ") - builder.WriteString(statement.JoinStr) + if statement.LimitN != nil { + _, err := fmt.Fprint(w, " LIMIT ", *statement.LimitN) + return err } - return &builder + // no limit statement + return nil } func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderBy bool) (string, []interface{}, error) { var ( - distinct string - dialect = statement.dialect - fromStr = statement.fromBuilder().String() - top, mssqlCondi, whereStr string + distinct string + dialect = statement.dialect + top, whereStr string + mssqlCondi = builder.NewWriter() ) if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") { distinct = "DISTINCT " } - condSQL, condArgs, err := statement.GenCondSQL(statement.cond) - if err != nil { + condWriter := builder.NewWriter() + if err := statement.cond.WriteTo(statement.QuoteReplacer(condWriter)); err != nil { return "", nil, err } - if len(condSQL) > 0 { - whereStr = fmt.Sprintf(" WHERE %s", condSQL) + + if condWriter.Len() > 0 { + whereStr = " WHERE " } pLimitN := statement.LimitN @@ -289,49 +271,81 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB } } - var orderStr string - if needOrderBy && len(statement.OrderStr) > 0 { - orderStr = fmt.Sprintf(" ORDER BY %s", statement.OrderStr) + if _, err := fmt.Fprintf(mssqlCondi, "(%s NOT IN (SELECT TOP %d %s", + column, statement.Start, column); err != nil { + return "", nil, err } - - var groupStr string - if len(statement.GroupByStr) > 0 { - groupStr = fmt.Sprintf(" GROUP BY %s", statement.GroupByStr) + if err := statement.writeFrom(mssqlCondi); err != nil { + return "", nil, err + } + if whereStr != "" { + if _, err := fmt.Fprint(mssqlCondi, whereStr); err != nil { + return "", nil, err + } + if err := utils.WriteBuilder(mssqlCondi, statement.QuoteReplacer(condWriter)); err != nil { + return "", nil, err + } + } + if needOrderBy { + if err := statement.WriteOrderBy(mssqlCondi); err != nil { + return "", nil, err + } + } + if err := statement.WriteGroupBy(mssqlCondi); err != nil { + return "", nil, err + } + if _, err := fmt.Fprint(mssqlCondi, "))"); err != nil { + return "", nil, err } - mssqlCondi = fmt.Sprintf("(%s NOT IN (SELECT TOP %d %s%s%s%s%s))", - column, statement.Start, column, fromStr, whereStr, orderStr, groupStr) } } - var buf strings.Builder - fmt.Fprintf(&buf, "SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr) - if len(mssqlCondi) > 0 { + buf := builder.NewWriter() + if _, err := fmt.Fprintf(buf, "SELECT %v%v%v", distinct, top, columnStr); err != nil { + return "", nil, err + } + if err := statement.writeFrom(buf); err != nil { + return "", nil, err + } + if whereStr != "" { + if _, err := fmt.Fprint(buf, whereStr); err != nil { + return "", nil, err + } + if err := utils.WriteBuilder(buf, statement.QuoteReplacer(condWriter)); err != nil { + return "", nil, err + } + } + if mssqlCondi.Len() > 0 { if len(whereStr) > 0 { - fmt.Fprint(&buf, " AND ", mssqlCondi) + if _, err := fmt.Fprint(buf, " AND "); err != nil { + return "", nil, err + } } else { - fmt.Fprint(&buf, " WHERE ", mssqlCondi) + if _, err := fmt.Fprint(buf, " WHERE "); err != nil { + return "", nil, err + } + } + + if err := utils.WriteBuilder(buf, mssqlCondi); err != nil { + return "", nil, err } } - if statement.GroupByStr != "" { - fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr) + if err := statement.WriteGroupBy(buf); err != nil { + return "", nil, err } - if statement.HavingStr != "" { - fmt.Fprint(&buf, " ", statement.HavingStr) + if err := statement.writeHaving(buf); err != nil { + return "", nil, err } - if needOrderBy && statement.OrderStr != "" { - fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) + if needOrderBy { + if err := statement.WriteOrderBy(buf); err != nil { + return "", nil, err + } } if needLimit { if dialect.URI().DBType != schemas.MSSQL && dialect.URI().DBType != schemas.ORACLE { - if statement.Start > 0 { - if pLimitN != nil { - fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", *pLimitN, statement.Start) - } else { - fmt.Fprintf(&buf, " LIMIT 0 OFFSET %v", statement.Start) - } - } else if pLimitN != nil { - fmt.Fprint(&buf, " LIMIT ", *pLimitN) + if err := statement.writeLimitOffset(buf); err != nil { + return "", nil, err } } else if dialect.URI().DBType == schemas.ORACLE { if pLimitN != nil { @@ -341,16 +355,16 @@ func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderB if rawColStr == "*" { rawColStr = "at.*" } - fmt.Fprintf(&buf, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d", + fmt.Fprintf(buf, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d", columnStr, rawColStr, oldString, statement.Start+*pLimitN, statement.Start) } } } if statement.IsForUpdate { - return dialect.ForUpdateSQL(buf.String()), condArgs, nil + return dialect.ForUpdateSQL(buf.String()), buf.Args(), nil } - return buf.String(), condArgs, nil + return buf.String(), buf.Args(), nil } // GenExistSQL generates Exist SQL @@ -359,10 +373,6 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac return statement.GenRawSQL(), statement.RawParams, nil } - var sqlStr string - var args []interface{} - var joinStr string - var err error var b interface{} if len(bean) > 0 { b = bean[0] @@ -381,45 +391,70 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac if len(tableName) <= 0 { return "", nil, ErrTableNotFound } - if statement.RefTable == nil { - tableName = statement.quote(tableName) - if len(statement.JoinStr) > 0 { - joinStr = statement.JoinStr - } + if statement.RefTable != nil { + return statement.Limit(1).GenGetSQL(b) + } + tableName = statement.quote(tableName) + + buf := builder.NewWriter() + if statement.dialect.URI().DBType == schemas.MSSQL { + if _, err := fmt.Fprintf(buf, "SELECT TOP 1 * FROM %s", tableName); err != nil { + return "", nil, err + } + if err := statement.writeJoin(buf); err != nil { + return "", nil, err + } if statement.Conds().IsValid() { - condSQL, condArgs, err := statement.GenCondSQL(statement.Conds()) - if err != nil { + if _, err := fmt.Fprintf(buf, " WHERE "); err != nil { return "", nil, err } - - if statement.dialect.URI().DBType == schemas.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL) - } else if statement.dialect.URI().DBType == schemas.ORACLE { - sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) %s AND ROWNUM=1", tableName, joinStr, condSQL) - } else { - sqlStr = fmt.Sprintf("SELECT 1 FROM %s %s WHERE %s LIMIT 1", tableName, joinStr, condSQL) + if err := statement.Conds().WriteTo(statement.QuoteReplacer(buf)); err != nil { + return "", nil, err } - args = condArgs - } else { - if statement.dialect.URI().DBType == schemas.MSSQL { - sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) - } else if statement.dialect.URI().DBType == schemas.ORACLE { - sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE ROWNUM=1", tableName, joinStr) - } else { - sqlStr = fmt.Sprintf("SELECT 1 FROM %s %s LIMIT 1", tableName, joinStr) + } + } else if statement.dialect.URI().DBType == schemas.ORACLE { + if _, err := fmt.Fprintf(buf, "SELECT * FROM %s", tableName); err != nil { + return "", nil, err + } + if err := statement.writeJoin(buf); err != nil { + return "", nil, err + } + if _, err := fmt.Fprintf(buf, " WHERE "); err != nil { + return "", nil, err + } + if statement.Conds().IsValid() { + if err := statement.Conds().WriteTo(statement.QuoteReplacer(buf)); err != nil { + return "", nil, err } - args = []interface{}{} + if _, err := fmt.Fprintf(buf, " AND "); err != nil { + return "", nil, err + } + } + if _, err := fmt.Fprintf(buf, "ROWNUM=1"); err != nil { + return "", nil, err } } else { - statement.Limit(1) - sqlStr, args, err = statement.GenGetSQL(b) - if err != nil { + if _, err := fmt.Fprintf(buf, "SELECT 1 FROM %s", tableName); err != nil { + return "", nil, err + } + if err := statement.writeJoin(buf); err != nil { + return "", nil, err + } + if statement.Conds().IsValid() { + if _, err := fmt.Fprintf(buf, " WHERE "); err != nil { + return "", nil, err + } + if err := statement.Conds().WriteTo(statement.QuoteReplacer(buf)); err != nil { + return "", nil, err + } + } + if _, err := fmt.Fprintf(buf, " LIMIT 1"); err != nil { return "", nil, err } } - return sqlStr, args, nil + return buf.String(), buf.Args(), nil } // GenFindSQL generates Find SQL @@ -428,15 +463,11 @@ func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interfa return statement.GenRawSQL(), statement.RawParams, nil } - var sqlStr string - var args []interface{} - var err error - if len(statement.TableName()) <= 0 { return "", nil, ErrTableNotFound } - var columnStr = statement.ColumnStr() + columnStr := statement.ColumnStr() if len(statement.SelectStr) > 0 { columnStr = statement.SelectStr } else { @@ -464,16 +495,5 @@ func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interfa statement.cond = statement.cond.And(autoCond) - sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true) - if err != nil { - return "", nil, err - } - args = append(statement.joinArgs, condArgs...) - // for mssql and use limit - qs := strings.Count(sqlStr, "?") - if len(args)*2 == qs { - args = append(args, args...) - } - - return sqlStr, args, nil + return statement.genSelectSQL(columnStr, true, true) } diff --git a/internal/statements/select.go b/internal/statements/select.go new file mode 100644 index 00000000..2bd2e94d --- /dev/null +++ b/internal/statements/select.go @@ -0,0 +1,137 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/xorm/schemas" +) + +// Select replace select +func (statement *Statement) Select(str string) *Statement { + statement.SelectStr = statement.ReplaceQuote(str) + return statement +} + +func col2NewCols(columns ...string) []string { + newColumns := make([]string, 0, len(columns)) + for _, col := range columns { + col = strings.Replace(col, "`", "", -1) + col = strings.Replace(col, `"`, "", -1) + ccols := strings.Split(col, ",") + for _, c := range ccols { + newColumns = append(newColumns, strings.TrimSpace(c)) + } + } + return newColumns +} + +// Cols generate "col1, col2" statement +func (statement *Statement) Cols(columns ...string) *Statement { + cols := col2NewCols(columns...) + for _, nc := range cols { + statement.ColumnMap.Add(nc) + } + return statement +} + +// ColumnStr returns column string +func (statement *Statement) ColumnStr() string { + return statement.dialect.Quoter().Join(statement.ColumnMap, ", ") +} + +// AllCols update use only: update all columns +func (statement *Statement) AllCols() *Statement { + statement.useAllCols = true + return statement +} + +// MustCols update use only: must update columns +func (statement *Statement) MustCols(columns ...string) *Statement { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.MustColumnMap[strings.ToLower(nc)] = true + } + return statement +} + +// UseBool indicates that use bool fields as update contents and query contiditions +func (statement *Statement) UseBool(columns ...string) *Statement { + if len(columns) > 0 { + statement.MustCols(columns...) + } else { + statement.allUseBool = true + } + return statement +} + +// Omit do not use the columns +func (statement *Statement) Omit(columns ...string) { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.OmitColumnMap = append(statement.OmitColumnMap, nc) + } +} + +func (statement *Statement) genColumnStr() string { + if statement.RefTable == nil { + return "" + } + + var buf strings.Builder + columns := statement.RefTable.Columns() + + for _, col := range columns { + if statement.OmitColumnMap.Contain(col.Name) { + continue + } + + if len(statement.ColumnMap) > 0 && !statement.ColumnMap.Contain(col.Name) { + continue + } + + if col.MapType == schemas.ONLYTODB { + continue + } + + if buf.Len() != 0 { + buf.WriteString(", ") + } + + if statement.JoinStr != "" { + if statement.TableAlias != "" { + buf.WriteString(statement.TableAlias) + } else { + buf.WriteString(statement.TableName()) + } + + buf.WriteString(".") + } + + statement.dialect.Quoter().QuoteTo(&buf, col.Name) + } + + return buf.String() +} + +func (statement *Statement) colName(col *schemas.Column, tableName string) string { + if statement.needTableName() { + nm := tableName + if len(statement.TableAlias) > 0 { + nm = statement.TableAlias + } + return fmt.Sprintf("%s.%s", statement.quote(nm), statement.quote(col.Name)) + } + return statement.quote(col.Name) +} + +// Distinct generates "DISTINCT col1, col2 " statement +func (statement *Statement) Distinct(columns ...string) *Statement { + statement.IsDistinct = true + statement.Cols(columns...) + return statement +} diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 3069561e..a8fe34fa 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -43,7 +43,8 @@ type Statement struct { Start int LimitN *int idParam schemas.PK - OrderStr string + orderStr string + orderArgs []interface{} JoinStr string joinArgs []interface{} GroupByStr string @@ -101,15 +102,6 @@ func (statement *Statement) GenRawSQL() string { return statement.ReplaceQuote(statement.RawSQL) } -// GenCondSQL generates condition SQL -func (statement *Statement) GenCondSQL(condOrBuilder interface{}) (string, []interface{}, error) { - condSQL, condArgs, err := builder.ToSQL(condOrBuilder) - if err != nil { - return "", nil, err - } - return statement.ReplaceQuote(condSQL), condArgs, nil -} - // ReplaceQuote replace sql key words with quote func (statement *Statement) ReplaceQuote(sql string) string { if sql == "" || statement.dialect.URI().DBType == schemas.MYSQL || @@ -129,7 +121,7 @@ func (statement *Statement) Reset() { statement.RefTable = nil statement.Start = 0 statement.LimitN = nil - statement.OrderStr = "" + statement.ResetOrderBy() statement.UseCascade = true statement.JoinStr = "" statement.joinArgs = make([]interface{}, 0) @@ -164,21 +156,6 @@ func (statement *Statement) Reset() { statement.LastError = nil } -// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function -func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement { - statement.NoAutoCondition = true - if len(no) > 0 { - statement.NoAutoCondition = no[0] - } - return statement -} - -// Alias set the table alias -func (statement *Statement) Alias(alias string) *Statement { - statement.TableAlias = alias - return statement -} - // SQL adds raw sql statement func (statement *Statement) SQL(query interface{}, args ...interface{}) *Statement { switch query.(type) { @@ -198,80 +175,10 @@ func (statement *Statement) SQL(query interface{}, args ...interface{}) *Stateme return statement } -// Where add Where statement -func (statement *Statement) Where(query interface{}, args ...interface{}) *Statement { - return statement.And(query, args...) -} - func (statement *Statement) quote(s string) string { return statement.dialect.Quoter().Quote(s) } -// And add Where & and statement -func (statement *Statement) And(query interface{}, args ...interface{}) *Statement { - switch qr := query.(type) { - case string: - cond := builder.Expr(qr, args...) - statement.cond = statement.cond.And(cond) - case map[string]interface{}: - cond := make(builder.Eq) - for k, v := range qr { - cond[statement.quote(k)] = v - } - statement.cond = statement.cond.And(cond) - case builder.Cond: - statement.cond = statement.cond.And(qr) - for _, v := range args { - if vv, ok := v.(builder.Cond); ok { - statement.cond = statement.cond.And(vv) - } - } - default: - statement.LastError = ErrConditionType - } - - return statement -} - -// Or add Where & Or statement -func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement { - switch qr := query.(type) { - case string: - cond := builder.Expr(qr, args...) - statement.cond = statement.cond.Or(cond) - case map[string]interface{}: - cond := make(builder.Eq) - for k, v := range qr { - cond[statement.quote(k)] = v - } - statement.cond = statement.cond.Or(cond) - case builder.Cond: - statement.cond = statement.cond.Or(qr) - for _, v := range args { - if vv, ok := v.(builder.Cond); ok { - statement.cond = statement.cond.Or(vv) - } - } - default: - statement.LastError = ErrConditionType - } - return statement -} - -// In generate "Where column IN (?) " statement -func (statement *Statement) In(column string, args ...interface{}) *Statement { - in := builder.In(statement.quote(column), args...) - statement.cond = statement.cond.And(in) - return statement -} - -// NotIn generate "Where column NOT IN (?) " statement -func (statement *Statement) NotIn(column string, args ...interface{}) *Statement { - notIn := builder.NotIn(statement.quote(column), args...) - statement.cond = statement.cond.And(notIn) - return statement -} - // SetRefValue set ref value func (statement *Statement) SetRefValue(v reflect.Value) error { var err error @@ -302,26 +209,6 @@ func (statement *Statement) needTableName() bool { return len(statement.JoinStr) > 0 } -func (statement *Statement) colName(col *schemas.Column, tableName string) string { - if statement.needTableName() { - nm := tableName - if len(statement.TableAlias) > 0 { - nm = statement.TableAlias - } - return fmt.Sprintf("%s.%s", statement.quote(nm), statement.quote(col.Name)) - } - return statement.quote(col.Name) -} - -// TableName return current tableName -func (statement *Statement) TableName() string { - if statement.AltTableName != "" { - return statement.AltTableName - } - - return statement.tableName -} - // Incr Generate "Update ... Set column = column + arg" statement func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { if len(arg) > 0 { @@ -352,85 +239,12 @@ func (statement *Statement) SetExpr(column string, expression interface{}) *Stat return statement } -// Distinct generates "DISTINCT col1, col2 " statement -func (statement *Statement) Distinct(columns ...string) *Statement { - statement.IsDistinct = true - statement.Cols(columns...) - return statement -} - // ForUpdate generates "SELECT ... FOR UPDATE" statement func (statement *Statement) ForUpdate() *Statement { statement.IsForUpdate = true return statement } -// Select replace select -func (statement *Statement) Select(str string) *Statement { - statement.SelectStr = statement.ReplaceQuote(str) - return statement -} - -func col2NewCols(columns ...string) []string { - newColumns := make([]string, 0, len(columns)) - for _, col := range columns { - col = strings.Replace(col, "`", "", -1) - col = strings.Replace(col, `"`, "", -1) - ccols := strings.Split(col, ",") - for _, c := range ccols { - newColumns = append(newColumns, strings.TrimSpace(c)) - } - } - return newColumns -} - -// Cols generate "col1, col2" statement -func (statement *Statement) Cols(columns ...string) *Statement { - cols := col2NewCols(columns...) - for _, nc := range cols { - statement.ColumnMap.Add(nc) - } - return statement -} - -// ColumnStr returns column string -func (statement *Statement) ColumnStr() string { - return statement.dialect.Quoter().Join(statement.ColumnMap, ", ") -} - -// AllCols update use only: update all columns -func (statement *Statement) AllCols() *Statement { - statement.useAllCols = true - return statement -} - -// MustCols update use only: must update columns -func (statement *Statement) MustCols(columns ...string) *Statement { - newColumns := col2NewCols(columns...) - for _, nc := range newColumns { - statement.MustColumnMap[strings.ToLower(nc)] = true - } - return statement -} - -// UseBool indicates that use bool fields as update contents and query contiditions -func (statement *Statement) UseBool(columns ...string) *Statement { - if len(columns) > 0 { - statement.MustCols(columns...) - } else { - statement.allUseBool = true - } - return statement -} - -// Omit do not use the columns -func (statement *Statement) Omit(columns ...string) { - newColumns := col2NewCols(columns...) - for _, nc := range newColumns { - statement.OmitColumnMap = append(statement.OmitColumnMap, nc) - } -} - // Nullable Update use only: update columns to null when value is nullable and zero-value func (statement *Statement) Nullable(columns ...string) { newColumns := col2NewCols(columns...) @@ -454,54 +268,6 @@ func (statement *Statement) Limit(limit int, start ...int) *Statement { return statement } -// OrderBy generate "Order By order" statement -func (statement *Statement) OrderBy(order string) *Statement { - if len(statement.OrderStr) > 0 { - statement.OrderStr += ", " - } - statement.OrderStr += statement.ReplaceQuote(order) - return statement -} - -// Desc generate `ORDER BY xx DESC` -func (statement *Statement) Desc(colNames ...string) *Statement { - var buf strings.Builder - if len(statement.OrderStr) > 0 { - fmt.Fprint(&buf, statement.OrderStr, ", ") - } - for i, col := range colNames { - if i > 0 { - fmt.Fprint(&buf, ", ") - } - _ = statement.dialect.Quoter().QuoteTo(&buf, col) - fmt.Fprint(&buf, " DESC") - } - statement.OrderStr = buf.String() - return statement -} - -// Asc provide asc order by query condition, the input parameters are columns. -func (statement *Statement) Asc(colNames ...string) *Statement { - var buf strings.Builder - if len(statement.OrderStr) > 0 { - fmt.Fprint(&buf, statement.OrderStr, ", ") - } - for i, col := range colNames { - if i > 0 { - fmt.Fprint(&buf, ", ") - } - _ = statement.dialect.Quoter().QuoteTo(&buf, col) - fmt.Fprint(&buf, " ASC") - } - statement.OrderStr = buf.String() - return statement -} - -// Conds returns condtions -func (statement *Statement) Conds() builder.Cond { - return statement.cond -} - // SetTable tempororily set table name, the parameter could be a string or a pointer of struct func (statement *Statement) SetTable(tableNameOrBean interface{}) error { v := rValue(tableNameOrBean) @@ -518,71 +284,34 @@ func (statement *Statement) SetTable(tableNameOrBean interface{}) error { return nil } -// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement { - var buf strings.Builder - if len(statement.JoinStr) > 0 { - fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) - } else { - fmt.Fprintf(&buf, "%v JOIN ", joinOP) - } - - switch tp := tablename.(type) { - case builder.Builder: - subSQL, subQueryArgs, err := tp.ToSQL() - if err != nil { - statement.LastError = err - return statement - } - - fields := strings.Split(tp.TableName(), ".") - aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) - aliasName = schemas.CommonQuoter.Trim(aliasName) - - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) - case *builder.Builder: - subSQL, subQueryArgs, err := tp.ToSQL() - if err != nil { - statement.LastError = err - return statement - } - - fields := strings.Split(tp.TableName(), ".") - aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) - aliasName = schemas.CommonQuoter.Trim(aliasName) - - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) - default: - tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) - if !utils.IsSubQuery(tbName) { - var buf strings.Builder - _ = statement.dialect.Quoter().QuoteTo(&buf, tbName) - tbName = buf.String() - } else { - tbName = statement.ReplaceQuote(tbName) - } - fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) - } - - statement.JoinStr = buf.String() - statement.joinArgs = append(statement.joinArgs, args...) - return statement -} - // GroupBy generate "Group By keys" statement func (statement *Statement) GroupBy(keys string) *Statement { statement.GroupByStr = statement.ReplaceQuote(keys) return statement } +func (statement *Statement) WriteGroupBy(w builder.Writer) error { + if statement.GroupByStr == "" { + return nil + } + _, err := fmt.Fprintf(w, " GROUP BY %s", statement.GroupByStr) + return err +} + // Having generate "Having conditions" statement func (statement *Statement) Having(conditions string) *Statement { statement.HavingStr = fmt.Sprintf("HAVING %v", statement.ReplaceQuote(conditions)) return statement } +func (statement *Statement) writeHaving(w builder.Writer) error { + if statement.HavingStr == "" { + return nil + } + _, err := fmt.Fprint(w, " ", statement.HavingStr) + return err +} + // SetUnscoped always disable struct tag "deleted" func (statement *Statement) SetUnscoped() *Statement { statement.unscoped = true @@ -594,47 +323,6 @@ func (statement *Statement) GetUnscoped() bool { return statement.unscoped } -func (statement *Statement) genColumnStr() string { - if statement.RefTable == nil { - return "" - } - - var buf strings.Builder - columns := statement.RefTable.Columns() - - for _, col := range columns { - if statement.OmitColumnMap.Contain(col.Name) { - continue - } - - if len(statement.ColumnMap) > 0 && !statement.ColumnMap.Contain(col.Name) { - continue - } - - if col.MapType == schemas.ONLYTODB { - continue - } - - if buf.Len() != 0 { - buf.WriteString(", ") - } - - if statement.JoinStr != "" { - if statement.TableAlias != "" { - buf.WriteString(statement.TableAlias) - } else { - buf.WriteString(statement.TableName()) - } - - buf.WriteString(".") - } - - statement.dialect.Quoter().QuoteTo(&buf, col.Name) - } - - return buf.String() -} - // GenIndexSQL generated create index SQL func (statement *Statement) GenIndexSQL() []string { var sqls []string @@ -914,7 +602,8 @@ func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, i statement.unscoped, statement.MustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) } -func (statement *Statement) mergeConds(bean interface{}) error { +// MergeConds merge conditions from bean and id +func (statement *Statement) MergeConds(bean interface{}) error { if !statement.NoAutoCondition && statement.RefTable != nil { addedTableName := (len(statement.JoinStr) > 0) autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName) @@ -927,15 +616,6 @@ func (statement *Statement) mergeConds(bean interface{}) error { return statement.ProcessIDParam() } -// GenConds generates conditions -func (statement *Statement) GenConds(bean interface{}) (string, []interface{}, error) { - if err := statement.mergeConds(bean); err != nil { - return "", nil, err - } - - return statement.GenCondSQL(statement.cond) -} - func (statement *Statement) quoteColumnStr(columnStr string) string { columns := strings.Split(columnStr, ",") return statement.dialect.Quoter().Join(columns, ",") diff --git a/internal/statements/table_name.go b/internal/statements/table_name.go new file mode 100644 index 00000000..8072a99d --- /dev/null +++ b/internal/statements/table_name.go @@ -0,0 +1,56 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package statements + +import ( + "fmt" + "strings" + + "xorm.io/builder" + "xorm.io/xorm/schemas" +) + +// TableName return current tableName +func (statement *Statement) TableName() string { + if statement.AltTableName != "" { + return statement.AltTableName + } + + return statement.tableName +} + +// Alias set the table alias +func (statement *Statement) Alias(alias string) *Statement { + statement.TableAlias = alias + return statement +} + +func (statement *Statement) writeAlias(w builder.Writer) error { + if statement.TableAlias != "" { + if statement.dialect.URI().DBType == schemas.ORACLE { + if _, err := fmt.Fprint(w, " ", statement.quote(statement.TableAlias)); err != nil { + return err + } + } else { + if _, err := fmt.Fprint(w, " AS ", statement.quote(statement.TableAlias)); err != nil { + return err + } + } + } + return nil +} + +func (statement *Statement) writeTableName(w builder.Writer) error { + if statement.dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { + if _, err := fmt.Fprint(w, statement.TableName()); err != nil { + return err + } + } else { + if _, err := fmt.Fprint(w, statement.quote(statement.TableName())); err != nil { + return err + } + } + return nil +} diff --git a/internal/utils/builder.go b/internal/utils/builder.go new file mode 100644 index 00000000..bc97526f --- /dev/null +++ b/internal/utils/builder.go @@ -0,0 +1,27 @@ +// Copyright 2022 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package utils + +import ( + "fmt" + + "xorm.io/builder" +) + +type BuildReader interface { + String() string + Args() []interface{} +} + +// WriteBuilder writes writers to one +func WriteBuilder(w *builder.BytesWriter, inputs ...BuildReader) error { + for _, input := range inputs { + if _, err := fmt.Fprint(w, input.String()); err != nil { + return err + } + w.Append(input.Args()...) + } + return nil +} diff --git a/session.go b/session.go index 64b98bfe..388678cd 100644 --- a/session.go +++ b/session.go @@ -275,8 +275,8 @@ func (session *Session) Limit(limit int, start ...int) *Session { // OrderBy provide order by query condition, the input parameter is the content // after order by on a sql statement. -func (session *Session) OrderBy(order string) *Session { - session.statement.OrderBy(order) +func (session *Session) OrderBy(order interface{}, args ...interface{}) *Session { + session.statement.OrderBy(order, args...) return session } diff --git a/session_delete.go b/session_delete.go index a0f420b1..322d5a44 100644 --- a/session_delete.go +++ b/session_delete.go @@ -9,7 +9,9 @@ import ( "fmt" "strconv" + "xorm.io/builder" "xorm.io/xorm/caches" + "xorm.io/xorm/internal/utils" "xorm.io/xorm/schemas" ) @@ -99,10 +101,9 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { } var ( - condSQL string - condArgs []interface{} - err error - bean interface{} + condWriter = builder.NewWriter() + err error + bean interface{} ) if len(beans) > 0 { bean = beans[0] @@ -116,115 +117,97 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { processor.BeforeDelete() } - condSQL, condArgs, err = session.statement.GenConds(bean) - } else { - condSQL, condArgs, err = session.statement.GenCondSQL(session.statement.Conds()) + if err = session.statement.MergeConds(bean); err != nil { + return 0, err + } } - if err != nil { + + if err = session.statement.Conds().WriteTo(session.statement.QuoteReplacer(condWriter)); err != nil { return 0, err } pLimitN := session.statement.LimitN - if len(condSQL) == 0 && (pLimitN == nil || *pLimitN == 0) { + if condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) { return 0, ErrNeedDeletedCond } - var tableNameNoQuote = session.statement.TableName() - var tableName = session.engine.Quote(tableNameNoQuote) - var table = session.statement.RefTable - var deleteSQL string - if len(condSQL) > 0 { - deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL) - } else { - deleteSQL = fmt.Sprintf("DELETE FROM %v", tableName) + tableNameNoQuote := session.statement.TableName() + tableName := session.engine.Quote(tableNameNoQuote) + table := session.statement.RefTable + deleteSQLWriter := builder.NewWriter() + fmt.Fprintf(deleteSQLWriter, "DELETE FROM %v", tableName) + if condWriter.Len() > 0 { + fmt.Fprintf(deleteSQLWriter, " WHERE %v", condWriter.String()) + deleteSQLWriter.Append(condWriter.Args()...) } - var orderSQL string - if len(session.statement.OrderStr) > 0 { - orderSQL += fmt.Sprintf(" ORDER BY %s", session.statement.OrderStr) + orderSQLWriter := builder.NewWriter() + if err := session.statement.WriteOrderBy(orderSQLWriter); err != nil { + return 0, err } + if pLimitN != nil && *pLimitN > 0 { limitNValue := *pLimitN - orderSQL += fmt.Sprintf(" LIMIT %d", limitNValue) + if _, err := fmt.Fprintf(orderSQLWriter, " LIMIT %d", limitNValue); err != nil { + return 0, err + } } - if len(orderSQL) > 0 { + orderCondWriter := builder.NewWriter() + if orderSQLWriter.Len() > 0 { switch session.engine.dialect.URI().DBType { case schemas.POSTGRES: - inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - deleteSQL += " AND " + inSQL + if condWriter.Len() > 0 { + fmt.Fprintf(orderCondWriter, " AND ") } else { - deleteSQL += " WHERE " + inSQL + fmt.Fprintf(orderCondWriter, " WHERE ") } + fmt.Fprintf(orderCondWriter, "ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQLWriter.String()) + orderCondWriter.Append(orderSQLWriter.Args()...) case schemas.SQLITE: - inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - deleteSQL += " AND " + inSQL + if condWriter.Len() > 0 { + fmt.Fprintf(orderCondWriter, " AND ") } else { - deleteSQL += " WHERE " + inSQL + fmt.Fprintf(orderCondWriter, " WHERE ") } + fmt.Fprintf(orderCondWriter, "rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQLWriter.String()) // TODO: how to handle delete limit on mssql? case schemas.MSSQL: return 0, ErrNotImplemented default: - deleteSQL += orderSQL + fmt.Fprint(orderCondWriter, orderSQLWriter.String()) + orderCondWriter.Append(orderSQLWriter.Args()...) } } - var realSQL string - argsForCache := make([]interface{}, 0, len(condArgs)*2) + realSQLWriter := builder.NewWriter() + argsForCache := make([]interface{}, 0, len(deleteSQLWriter.Args())*2) + copy(argsForCache, deleteSQLWriter.Args()) + argsForCache = append(deleteSQLWriter.Args(), argsForCache...) if session.statement.GetUnscoped() || table == nil || table.DeletedColumn() == nil { // tag "deleted" is disabled - realSQL = deleteSQL - copy(argsForCache, condArgs) - argsForCache = append(condArgs, argsForCache...) + if err := utils.WriteBuilder(realSQLWriter, deleteSQLWriter, orderCondWriter); err != nil { + return 0, err + } } else { - // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for caches. - copy(argsForCache, condArgs) - argsForCache = append(condArgs, argsForCache...) - deletedColumn := table.DeletedColumn() - realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", + if _, err := fmt.Fprintf(realSQLWriter, "UPDATE %v SET %v = ? WHERE %v", session.engine.Quote(session.statement.TableName()), session.engine.Quote(deletedColumn.Name), - condSQL) - - if len(orderSQL) > 0 { - switch session.engine.dialect.URI().DBType { - case schemas.POSTGRES: - inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - realSQL += " AND " + inSQL - } else { - realSQL += " WHERE " + inSQL - } - case schemas.SQLITE: - inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) - if len(condSQL) > 0 { - realSQL += " AND " + inSQL - } else { - realSQL += " WHERE " + inSQL - } - // TODO: how to handle delete limit on mssql? - case schemas.MSSQL: - return 0, ErrNotImplemented - default: - realSQL += orderSQL - } + condWriter.String()); err != nil { + return 0, err } - - // !oinume! Insert nowTime to the head of session.statement.Params - condArgs = append(condArgs, "") - paramsLen := len(condArgs) - copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1]) - val, t, err := session.engine.nowTime(deletedColumn) if err != nil { return 0, err } - condArgs[0] = val + realSQLWriter.Append(val) + realSQLWriter.Append(condWriter.Args()...) - var colName = deletedColumn.Name + if err := utils.WriteBuilder(realSQLWriter, orderCondWriter); err != nil { + return 0, err + } + + colName := deletedColumn.Name session.afterClosures = append(session.afterClosures, func(bean interface{}) { col := table.GetColumn(colName) setColumnTime(bean, col, t) @@ -232,11 +215,11 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { } if cacher := session.engine.GetCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache { - _ = session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...) + _ = session.cacheDelete(table, tableNameNoQuote, deleteSQLWriter.String(), argsForCache...) } session.statement.RefTable = table - res, err := session.exec(realSQL, condArgs...) + res, err := session.exec(realSQLWriter.String(), realSQLWriter.Args()...) if err != nil { return 0, err } diff --git a/session_find.go b/session_find.go index caf79ee3..2270454b 100644 --- a/session_find.go +++ b/session_find.go @@ -60,9 +60,7 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte if len(session.statement.ColumnMap) > 0 && !session.statement.IsDistinct { session.statement.ColumnMap = []string{} } - if session.statement.OrderStr != "" { - session.statement.OrderStr = "" - } + session.statement.ResetOrderBy() if session.statement.LimitN != nil { session.statement.LimitN = nil } @@ -85,15 +83,15 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) } sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) - var isSlice = sliceValue.Kind() == reflect.Slice - var isMap = sliceValue.Kind() == reflect.Map + isSlice := sliceValue.Kind() == reflect.Slice + isMap := sliceValue.Kind() == reflect.Map if !isSlice && !isMap { return errors.New("needs a pointer to a slice or a map") } sliceElementType := sliceValue.Type().Elem() - var tp = tpStruct + tp := tpStruct if session.statement.RefTable == nil { if sliceElementType.Kind() == reflect.Ptr { if sliceElementType.Elem().Kind() == reflect.Struct { @@ -190,7 +188,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect return err } - var newElemFunc = func(fields []string) reflect.Value { + newElemFunc := func(fields []string) reflect.Value { return utils.New(elemType, len(fields), len(fields)) } @@ -235,7 +233,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect } if elemType.Kind() == reflect.Struct { - var newValue = newElemFunc(fields) + newValue := newElemFunc(fields) tb, err := session.engine.tagParser.ParseWithCache(newValue) if err != nil { return err @@ -249,7 +247,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect } for rows.Next() { - var newValue = newElemFunc(fields) + newValue := newElemFunc(fields) bean := newValue.Interface() switch elemType.Kind() { @@ -310,7 +308,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in session.engine.logger.Debugf("[cacheFind] ids length > 500, no cache") return ErrCacheFailed } - var res = make([]string, len(table.PrimaryKeys)) + res := make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { return err @@ -342,7 +340,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in ididxes := make(map[string]int) var ides []schemas.PK - var temps = make([]interface{}, len(ids)) + temps := make([]interface{}, len(ids)) for idx, id := range ids { sid, err := id.ToString() @@ -457,7 +455,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean)))) } } else if sliceValue.Kind() == reflect.Map { - var key = ids[j] + key := ids[j] keyType := sliceValue.Type().Key() keyValue := reflect.New(keyType) var ikey interface{} diff --git a/session_update.go b/session_update.go index fefbee90..76f311d6 100644 --- a/session_update.go +++ b/session_update.go @@ -60,7 +60,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri ids = make([]schemas.PK, 0) for rows.Next() { - var res = make([]string, len(table.PrimaryKeys)) + res := make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { return err @@ -176,8 +176,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 // -- var err error - var isMap = t.Kind() == reflect.Map - var isStruct = t.Kind() == reflect.Struct + isMap := t.Kind() == reflect.Map + isStruct := t.Kind() == reflect.Struct if isStruct { if err := session.statement.SetRefBean(bean); err != nil { return 0, err @@ -226,7 +226,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 args = append(args, val) } - var colName = col.Name + colName := col.Name if isStruct { session.afterClosures = append(session.afterClosures, func(bean interface{}) { col := table.GetColumn(colName) @@ -258,10 +258,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } colNames = append(colNames, session.engine.Quote(expr.ColName)+"="+tp) case *builder.Builder: - subQuery, subArgs, err := session.statement.GenCondSQL(tp) + subQuery, subArgs, err := builder.ToSQL(tp) if err != nil { return 0, err } + subQuery = session.statement.ReplaceQuote(subQuery) colNames = append(colNames, session.engine.Quote(expr.ColName)+"=("+subQuery+")") args = append(args, subArgs...) default: @@ -279,7 +280,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 condBeanIsStruct := false if len(condiBean) > 0 { if c, ok := condiBean[0].(map[string]interface{}); ok { - var eq = make(builder.Eq) + eq := make(builder.Eq) for k, v := range c { eq[session.engine.Quote(k)] = v } @@ -323,11 +324,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 st := session.statement var ( - sqlStr string - condArgs []interface{} - condSQL string cond = session.statement.Conds().And(autoCond) - doIncVer = isStruct && (table != nil && table.Version != "" && session.statement.CheckVersion) verValue *reflect.Value ) @@ -347,70 +344,65 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 return 0, ErrNoColumnsTobeUpdated } - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + whereWriter := builder.NewWriter() + if cond.IsValid() { + fmt.Fprint(whereWriter, "WHERE ") + } + if err := cond.WriteTo(st.QuoteReplacer(whereWriter)); err != nil { + return 0, err + } + if err := st.WriteOrderBy(whereWriter); err != nil { return 0, err } - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } - - if st.OrderStr != "" { - condSQL += fmt.Sprintf(" ORDER BY %v", st.OrderStr) - } - - var tableName = session.statement.TableName() + tableName := session.statement.TableName() // TODO: Oracle support needed var top string if st.LimitN != nil { limitValue := *st.LimitN switch session.engine.dialect.URI().DBType { case schemas.MYSQL: - condSQL += fmt.Sprintf(" LIMIT %d", limitValue) + fmt.Fprintf(whereWriter, " LIMIT %d", limitValue) case schemas.SQLITE: - tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) + fmt.Fprintf(whereWriter, " LIMIT %d", limitValue) + cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", - session.engine.Quote(tableName), tempCondSQL), condArgs...)) - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + session.engine.Quote(tableName), whereWriter.String()), whereWriter.Args()...)) + + whereWriter = builder.NewWriter() + fmt.Fprint(whereWriter, "WHERE ") + if err := cond.WriteTo(st.QuoteReplacer(whereWriter)); err != nil { return 0, err } - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } case schemas.POSTGRES: - tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) + fmt.Fprintf(whereWriter, " LIMIT %d", limitValue) + cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)", - session.engine.Quote(tableName), tempCondSQL), condArgs...)) - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + session.engine.Quote(tableName), whereWriter.String()), whereWriter.Args()...)) + + whereWriter = builder.NewWriter() + fmt.Fprint(whereWriter, "WHERE ") + if err := cond.WriteTo(st.QuoteReplacer(whereWriter)); err != nil { return 0, err } - - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } case schemas.MSSQL: - if st.OrderStr != "" && table != nil && len(table.PrimaryKeys) == 1 { + if st.HasOrderBy() && table != nil && len(table.PrimaryKeys) == 1 { cond = builder.Expr(fmt.Sprintf("%s IN (SELECT TOP (%d) %s FROM %v%v)", table.PrimaryKeys[0], limitValue, table.PrimaryKeys[0], - session.engine.Quote(tableName), condSQL), condArgs...) + session.engine.Quote(tableName), whereWriter.String()), whereWriter.Args()...) - condSQL, condArgs, err = session.statement.GenCondSQL(cond) - if err != nil { + whereWriter = builder.NewWriter() + fmt.Fprint(whereWriter, "WHERE ") + if err := cond.WriteTo(whereWriter); err != nil { return 0, err } - if len(condSQL) > 0 { - condSQL = "WHERE " + condSQL - } } else { top = fmt.Sprintf("TOP (%d) ", limitValue) } } } - var tableAlias = session.engine.Quote(tableName) + tableAlias := session.engine.Quote(tableName) var fromSQL string if session.statement.TableAlias != "" { switch session.engine.dialect.URI().DBType { @@ -422,14 +414,19 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } - sqlStr = fmt.Sprintf("UPDATE %v%v SET %v %v%v", + updateWriter := builder.NewWriter() + if _, err := fmt.Fprintf(updateWriter, "UPDATE %v%v SET %v %v", top, tableAlias, strings.Join(colNames, ", "), - fromSQL, - condSQL) + fromSQL); err != nil { + return 0, err + } + if err := utils.WriteBuilder(updateWriter, whereWriter); err != nil { + return 0, err + } - res, err := session.exec(sqlStr, append(args, condArgs...)...) + res, err := session.exec(updateWriter.String(), append(args, updateWriter.Args()...)...) if err != nil { return 0, err } else if doIncVer { @@ -535,7 +532,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac } args = append(args, val) - var colName = col.Name + colName := col.Name session.afterClosures = append(session.afterClosures, func(bean interface{}) { col := table.GetColumn(colName) setColumnTime(bean, col, t) From c98930f8f2a5827f54376b5e23467d29acca6ef5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 3 Jun 2022 15:24:24 +0800 Subject: [PATCH 02/25] Fix oid index for postgres (#2154) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2154 --- dialects/postgres.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 83e4187f..ba73aad7 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -862,11 +862,11 @@ func (db *postgres) needQuote(name string) bool { func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = postgresQuoter + q := postgresQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = postgresQuoter + q := postgresQuoter q.IsReserved = db.needQuote db.quoter = q case QuotePolicyAlways: @@ -1125,7 +1125,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A col.Name = strings.Trim(colName, `" `) if colDefault != nil { - var theDefault = *colDefault + theDefault := *colDefault // cockroach has type with the default value with ::: // and postgres with ::, we should remove them before store them idx := strings.Index(theDefault, ":::") @@ -1301,15 +1301,8 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN } colNames = getIndexColName(indexdef) - isSkip := false - //Oid It's a special index. You can't put it in - for _, element := range colNames { - if "oid" == element { - isSkip = true - break - } - } - if isSkip { + // Oid It's a special index. You can't put it in. TODO: This is not perfect. + if indexName == tableName+"_oid_index" && len(colNames) == 1 && colNames[0] == "oid" { continue } From f469d8816644fdb21ad3c6b6da85514daa23219f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 3 Jun 2022 17:44:49 +0800 Subject: [PATCH 03/25] Update changelog for 1.3.1 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fed4e261..ae213c99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.3.1](https://gitea.com/xorm/xorm/releases/tag/1.3.1) - 2022-06-03 + +* BREAKING + * Refactor orderby and support arguments (#2150) + * return a clear error for set TEXT type as compare condition (#2062) +* BUGFIXES + * Fix oid index for postgres (#2154) + * Add ORDER BY SEQ_IN_INDEX to MySQL GetIndexes to Fix IndexTests (#2152) + * some improvement (#2136) +* ENHANCEMENTS + * Add interface to allow structs to provide specific index information (#2137) + * MySQL/MariaDB: return max length for text columns (#2133) + * PostgreSQL: enable comment on column (#2131) +* TESTING + * Add test for find date (#2121) + ## [1.3.0](https://gitea.com/xorm/xorm/releases/tag/1.3.0) - 2022-04-14 * BREAKING From c3bce556200f3356803beec9147210ba46319f99 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 14 Jul 2022 13:55:24 +0800 Subject: [PATCH 04/25] Change schemas.Column to use int64 (#2160) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2160 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/dameng.go | 26 +++++++-------- dialects/mssql.go | 14 ++++---- dialects/mysql.go | 12 +++---- dialects/oracle.go | 20 +++++------ dialects/postgres.go | 10 +++--- dialects/time.go | 12 ++++--- integrations/engine_test.go | 8 ++--- schemas/column.go | 8 ++--- schemas/type.go | 4 +-- tags/tag.go | 66 ++++++++++++++++++------------------- 10 files changed, 89 insertions(+), 91 deletions(-) diff --git a/dialects/dameng.go b/dialects/dameng.go index f4a075d5..5e92ec2f 100644 --- a/dialects/dameng.go +++ b/dialects/dameng.go @@ -622,9 +622,9 @@ func (db *dameng) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -729,11 +729,11 @@ func (db *dameng) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl func (db *dameng) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = damengQuoter + q := damengQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = damengQuoter + q := damengQuoter q.IsReserved = db.IsReserved db.quoter = q case QuotePolicyAlways: @@ -792,7 +792,7 @@ type dmClobObject interface { ReadString(int, int) (string, error) } -//var _ dmClobObject = &dm.DmClob{} +// var _ dmClobObject = &dm.DmClob{} func (d *dmClobScanner) Scan(data interface{}) error { if data == nil { @@ -927,7 +927,7 @@ func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableNam var ( ignore bool dt string - len1, len2 int + len1, len2 int64 ) dts := strings.Split(dataType.String, "(") @@ -935,10 +935,10 @@ func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableNam if len(dts) > 1 { lens := strings.Split(dts[1][:len(dts[1])-1], ",") if len(lens) > 1 { - len1, _ = strconv.Atoi(lens[0]) - len2, _ = strconv.Atoi(lens[1]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) + len2, _ = strconv.ParseInt(lens[1], 10, 64) } else { - len1, _ = strconv.Atoi(lens[0]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) } } @@ -972,9 +972,9 @@ func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableNam } if col.SQLType.Name == "TIMESTAMP" { - col.Length = int(dataScale.Int64) + col.Length = dataScale.Int64 } else { - col.Length = int(dataLen.Int64) + col.Length = dataLen.Int64 } if col.SQLType.IsTime() { @@ -1140,8 +1140,8 @@ func (d *damengDriver) GenScanResult(colType string) (interface{}, error) { } func (d *damengDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, vv ...interface{}) error { - var scanResults = make([]interface{}, 0, len(types)) - var replaces = make([]bool, 0, len(types)) + scanResults := make([]interface{}, 0, len(types)) + replaces := make([]bool, 0, len(types)) var err error for i, v := range vv { var replaced bool diff --git a/dialects/mssql.go b/dialects/mssql.go index 706a754a..1b6fe692 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -229,7 +229,7 @@ func (db *mssql) Init(uri *URI) error { func (db *mssql) SetParams(params map[string]string) { defaultVarchar, ok := params["DEFAULT_VARCHAR"] if ok { - var t = strings.ToUpper(defaultVarchar) + t := strings.ToUpper(defaultVarchar) switch t { case "NVARCHAR", "VARCHAR": db.defaultVarchar = t @@ -242,7 +242,7 @@ func (db *mssql) SetParams(params map[string]string) { defaultChar, ok := params["DEFAULT_CHAR"] if ok { - var t = strings.ToUpper(defaultChar) + t := strings.ToUpper(defaultChar) switch t { case "NCHAR", "CHAR": db.defaultChar = t @@ -375,9 +375,9 @@ func (db *mssql) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -403,11 +403,11 @@ func (db *mssql) IsReserved(name string) bool { func (db *mssql) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = mssqlQuoter + q := mssqlQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = mssqlQuoter + q := mssqlQuoter q.IsReserved = db.IsReserved db.quoter = q case QuotePolicyAlways: @@ -475,7 +475,7 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName colSeq := make([]string, 0) for rows.Next() { var name, ctype, vdefault string - var maxLen, precision, scale int + var maxLen, precision, scale int64 var nullable, isPK, defaultIsNull, isIncrement bool err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &defaultIsNull, &vdefault, &isPK, &isIncrement) if err != nil { diff --git a/dialects/mysql.go b/dialects/mysql.go index 31e7b788..6ed4a1be 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -330,9 +330,9 @@ func (db *mysql) SQLType(c *schemas.Column) string { } if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } if isUnsigned { @@ -444,7 +444,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName // Remove the /* mariadb-5.3 */ suffix from coltypes colName = strings.TrimSuffix(colName, "/* mariadb-5.3 */") colType = strings.ToUpper(colName) - var len1, len2 int + var len1, len2 int64 if len(cts) == 2 { idx := strings.Index(cts[1], ")") if colType == schemas.Enum && cts[1][0] == '\'' { // enum @@ -465,12 +465,12 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } } else { lens := strings.Split(cts[1][0:idx], ",") - len1, err = strconv.Atoi(strings.TrimSpace(lens[0])) + len1, err = strconv.ParseInt(strings.TrimSpace(lens[0]), 10, 64) if err != nil { return nil, nil, err } if len(lens) == 2 { - len2, err = strconv.Atoi(lens[1]) + len2, err = strconv.ParseInt(lens[1], 10, 64) if err != nil { return nil, nil, err } @@ -479,7 +479,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName } else { switch colType { case "MEDIUMTEXT", "LONGTEXT", "TEXT": - len1, err = strconv.Atoi(*maxLength) + len1, err = strconv.ParseInt(*maxLength, 10, 64) if err != nil { return nil, nil, err } diff --git a/dialects/oracle.go b/dialects/oracle.go index 04652bd6..8328ff15 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -570,9 +570,9 @@ func (db *oracle) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -606,7 +606,7 @@ func (db *oracle) DropTableSQL(tableName string) (string, bool) { } func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { - var sql = "CREATE TABLE " + sql := "CREATE TABLE " if tableName == "" { tableName = table.Name } @@ -641,11 +641,11 @@ func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: - var q = oracleQuoter + q := oracleQuoter q.IsReserved = schemas.AlwaysNoReserve db.quoter = q case QuotePolicyReserved: - var q = oracleQuoter + q := oracleQuoter q.IsReserved = db.IsReserved db.quoter = q case QuotePolicyAlways: @@ -690,7 +690,7 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam col.Indexes = make(map[string]int) var colName, colDefault, nullable, dataType, dataPrecision, dataScale *string - var dataLen int + var dataLen int64 err = rows.Scan(&colName, &colDefault, &dataType, &dataLen, &dataPrecision, &dataScale, &nullable) @@ -713,16 +713,16 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam var ignore bool var dt string - var len1, len2 int + var len1, len2 int64 dts := strings.Split(*dataType, "(") dt = dts[0] if len(dts) > 1 { lens := strings.Split(dts[1][:len(dts[1])-1], ",") if len(lens) > 1 { - len1, _ = strconv.Atoi(lens[0]) - len2, _ = strconv.Atoi(lens[1]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) + len2, _ = strconv.ParseInt(lens[1], 10, 64) } else { - len1, _ = strconv.Atoi(lens[0]) + len1, _ = strconv.ParseInt(lens[0], 10, 64) } } diff --git a/dialects/postgres.go b/dialects/postgres.go index ba73aad7..3c7ecb35 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -934,9 +934,9 @@ func (db *postgres) SQLType(c *schemas.Column) string { hasLen2 := (c.Length2 > 0) if hasLen2 { - res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" } else if hasLen1 { - res += "(" + strconv.Itoa(c.Length) + ")" + res += "(" + strconv.FormatInt(c.Length, 10) + ")" } return res } @@ -1110,9 +1110,9 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A return nil, nil, err } - var maxLen int + var maxLen int64 if maxLenStr != nil { - maxLen, err = strconv.Atoi(*maxLenStr) + maxLen, err = strconv.ParseInt(*maxLenStr, 10, 64) if err != nil { return nil, nil, err } @@ -1186,7 +1186,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A startIdx := strings.Index(strings.ToLower(dataType), "string(") if startIdx != -1 && strings.HasSuffix(dataType, ")") { length := dataType[startIdx+8 : len(dataType)-1] - l, _ := strconv.Atoi(length) + l, _ := strconv.ParseInt(length, 10, 64) col.SQLType = schemas.SQLType{Name: "STRING", DefaultLength: l, DefaultLength2: 0} } else { col.SQLType = schemas.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} diff --git a/dialects/time.go b/dialects/time.go index f0bbb765..cdc896be 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -23,7 +23,7 @@ func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.C } } - var tmZone = dbLocation + tmZone := dbLocation if col.TimeZone != nil { tmZone = col.TimeZone } @@ -34,15 +34,17 @@ func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.C case schemas.Date: return t.Format("2006-01-02"), nil case schemas.Time: - var layout = "15:04:05" + layout := "15:04:05" if col.Length > 0 { - layout += "." + strings.Repeat("0", col.Length) + // we can use int(...) casting here as it's very unlikely to a huge sized field + layout += "." + strings.Repeat("0", int(col.Length)) } return t.Format(layout), nil case schemas.DateTime, schemas.TimeStamp: - var layout = "2006-01-02 15:04:05" + layout := "2006-01-02 15:04:05" if col.Length > 0 { - layout += "." + strings.Repeat("0", col.Length) + // we can use int(...) casting here as it's very unlikely to a huge sized field + layout += "." + strings.Repeat("0", int(col.Length)) } return t.Format(layout), nil case schemas.Varchar: diff --git a/integrations/engine_test.go b/integrations/engine_test.go index 905c4f24..730a424e 100644 --- a/integrations/engine_test.go +++ b/integrations/engine_test.go @@ -290,13 +290,11 @@ func TestGetColumnsComment(t *testing.T) { } func TestGetColumnsLength(t *testing.T) { - var max_length int + var max_length int64 switch testEngine.Dialect().URI().DBType { - case - schemas.POSTGRES: + case schemas.POSTGRES: max_length = 0 - case - schemas.MYSQL: + case schemas.MYSQL: max_length = 65535 default: t.Skip() diff --git a/schemas/column.go b/schemas/column.go index 4bbb6c2d..001769cd 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -26,8 +26,8 @@ type Column struct { FieldIndex []int // Available only when parsed from a struct SQLType SQLType IsJSON bool - Length int - Length2 int + Length int64 + Length2 int64 Nullable bool Default string Indexes map[string]int @@ -48,7 +48,7 @@ type Column struct { } // NewColumn creates a new column -func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { +func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int64, nullable bool) *Column { return &Column{ Name: name, IsJSON: sqlType.IsJson(), @@ -82,7 +82,7 @@ func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) { // ValueOfV returns column's filed of struct's value accept reflevt value func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { - var v = *dataStruct + v := *dataStruct for _, i := range col.FieldIndex { if v.Kind() == reflect.Ptr { if v.IsNil() { diff --git a/schemas/type.go b/schemas/type.go index 8702862a..b8b30851 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -28,8 +28,8 @@ const ( // SQLType represents SQL types type SQLType struct { Name string - DefaultLength int - DefaultLength2 int + DefaultLength int64 + DefaultLength2 int64 } // enumerates all columns types diff --git a/tags/tag.go b/tags/tag.go index 4e1f1ce7..55f5f4cf 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -99,33 +99,31 @@ type Context struct { // Handler describes tag handler for XORM type Handler func(ctx *Context) error -var ( - // defaultTagHandlers enumerates all the default tag handler - defaultTagHandlers = map[string]Handler{ - "-": IgnoreHandler, - "<-": OnlyFromDBTagHandler, - "->": OnlyToDBTagHandler, - "PK": PKTagHandler, - "NULL": NULLTagHandler, - "NOT": NotTagHandler, - "AUTOINCR": AutoIncrTagHandler, - "DEFAULT": DefaultTagHandler, - "CREATED": CreatedTagHandler, - "UPDATED": UpdatedTagHandler, - "DELETED": DeletedTagHandler, - "VERSION": VersionTagHandler, - "UTC": UTCTagHandler, - "LOCAL": LocalTagHandler, - "NOTNULL": NotNullTagHandler, - "INDEX": IndexTagHandler, - "UNIQUE": UniqueTagHandler, - "CACHE": CacheTagHandler, - "NOCACHE": NoCacheTagHandler, - "COMMENT": CommentTagHandler, - "EXTENDS": ExtendsTagHandler, - "UNSIGNED": UnsignedTagHandler, - } -) +// defaultTagHandlers enumerates all the default tag handler +var defaultTagHandlers = map[string]Handler{ + "-": IgnoreHandler, + "<-": OnlyFromDBTagHandler, + "->": OnlyToDBTagHandler, + "PK": PKTagHandler, + "NULL": NULLTagHandler, + "NOT": NotTagHandler, + "AUTOINCR": AutoIncrTagHandler, + "DEFAULT": DefaultTagHandler, + "CREATED": CreatedTagHandler, + "UPDATED": UpdatedTagHandler, + "DELETED": DeletedTagHandler, + "VERSION": VersionTagHandler, + "UTC": UTCTagHandler, + "LOCAL": LocalTagHandler, + "NOTNULL": NotNullTagHandler, + "INDEX": IndexTagHandler, + "UNIQUE": UniqueTagHandler, + "CACHE": CacheTagHandler, + "NOCACHE": NoCacheTagHandler, + "COMMENT": CommentTagHandler, + "EXTENDS": ExtendsTagHandler, + "UNSIGNED": UnsignedTagHandler, +} func init() { for k := range schemas.SqlTypes { @@ -312,16 +310,16 @@ func SQLTypeTagHandler(ctx *Context) error { default: var err error if len(ctx.params) == 2 { - ctx.col.Length, err = strconv.Atoi(ctx.params[0]) + ctx.col.Length, err = strconv.ParseInt(ctx.params[0], 10, 64) if err != nil { return err } - ctx.col.Length2, err = strconv.Atoi(ctx.params[1]) + ctx.col.Length2, err = strconv.ParseInt(ctx.params[1], 10, 64) if err != nil { return err } } else if len(ctx.params) == 1 { - ctx.col.Length, err = strconv.Atoi(ctx.params[0]) + ctx.col.Length, err = strconv.ParseInt(ctx.params[0], 10, 64) if err != nil { return err } @@ -332,8 +330,8 @@ func SQLTypeTagHandler(ctx *Context) error { // ExtendsTagHandler describes extends tag handler func ExtendsTagHandler(ctx *Context) error { - var fieldValue = ctx.fieldValue - var isPtr = false + fieldValue := ctx.fieldValue + isPtr := false switch fieldValue.Kind() { case reflect.Ptr: f := fieldValue.Type().Elem() @@ -355,7 +353,7 @@ func ExtendsTagHandler(ctx *Context) error { col.FieldName = fmt.Sprintf("%v.%v", ctx.col.FieldName, col.FieldName) col.FieldIndex = append(ctx.col.FieldIndex, col.FieldIndex...) - var tagPrefix = ctx.col.FieldName + tagPrefix := ctx.col.FieldName if len(ctx.params) > 0 { col.Nullable = isPtr tagPrefix = strings.Trim(ctx.params[0], "'") @@ -378,7 +376,7 @@ func ExtendsTagHandler(ctx *Context) error { } } default: - //TODO: warning + // TODO: warning } return ErrIgnoreField } From c900ecc87fbd1e29a496a27d103adb07cfa9971f Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 3 Sep 2022 10:12:17 +0800 Subject: [PATCH 05/25] Prevent Sync failure with non-regular indexes on Postgres (#2174) When dropping indexes in Postgres if the index is non-regular we should not attempt to regularise the index name as it is already correct. Signed-off-by: Andrew Thornton Reviewed-on: https://gitea.com/xorm/xorm/pulls/2174 Reviewed-by: Lunny Xiao Co-authored-by: Andrew Thornton Co-committed-by: Andrew Thornton --- dialects/postgres.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index 3c7ecb35..f9de5859 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1030,11 +1030,10 @@ func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") tableName = tableParts[len(tableParts)-1] - if !strings.HasPrefix(idxName, "UQE_") && - !strings.HasPrefix(idxName, "IDX_") { - if index.Type == schemas.UniqueType { + if index.IsRegular { + if index.Type == schemas.UniqueType && !strings.HasPrefix(idxName, "UQE_") { idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) - } else { + } else if index.Type == schemas.IndexType && !strings.HasPrefix(idxName, "IDX_") { idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } } From bd58520020dfb5bd6b7f5779e871d53aa9ee4c71 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 3 Sep 2022 15:14:19 +0800 Subject: [PATCH 06/25] add changelog for 1.3.2 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae213c99..6887cb97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log. +## [1.3.2](https://gitea.com/xorm/xorm/releases/tag/1.3.2) - 2022-09-03 + +* BUGFIXES + * Change schemas.Column to use int64 (#2160) +* MISC + * Prevent Sync failure with non-regular indexes on Postgres (#2174) + ## [1.3.1](https://gitea.com/xorm/xorm/releases/tag/1.3.1) - 2022-06-03 * BREAKING From 3acabdaf26fcf58fa51e9067235c0c1d456e66b5 Mon Sep 17 00:00:00 2001 From: stevefan1999 Date: Mon, 24 Oct 2022 11:29:54 +0800 Subject: [PATCH 07/25] Fix Oracle Table creation default value (#2190) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2190 Reviewed-by: Lunny Xiao Co-authored-by: stevefan1999 Co-committed-by: stevefan1999 --- dialects/oracle.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dialects/oracle.go b/dialects/oracle.go index 8328ff15..ce91cd5d 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -548,7 +548,14 @@ func (db *oracle) Features() *DialectFeatures { func (db *oracle) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Bool, schemas.Serial, schemas.BigSerial: + case schemas.Bool: + if c.Default == "true" { + c.Default = "1" + } else if c.Default == "false" { + c.Default = "0" + } + res = "NUMBER(1,0)" + case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Serial, schemas.BigSerial: res = "NUMBER" case schemas.Binary, schemas.VarBinary, schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea: return schemas.Blob From 71a5939c65fa40906fa8bc5b194349e7cd018524 Mon Sep 17 00:00:00 2001 From: tylerthail2019 Date: Wed, 16 Nov 2022 13:22:04 +0800 Subject: [PATCH 08/25] add disable version check func (#2197) Co-authored-by: tyler Reviewed-on: https://gitea.com/xorm/xorm/pulls/2197 Reviewed-by: Lunny Xiao Co-authored-by: tylerthail2019 Co-committed-by: tylerthail2019 --- session.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/session.go b/session.go index 388678cd..854a125a 100644 --- a/session.go +++ b/session.go @@ -794,3 +794,9 @@ func (session *Session) PingContext(ctx context.Context) error { session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName()) return session.DB().PingContext(ctx) } + +// disable version check +func (session *Session) NoVersionCheck() *Session { + session.statement.CheckVersion = false + return session +} From f1bfc5ce983063d6262c125e68f59599541a2e6b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 9 Dec 2022 23:37:26 +0800 Subject: [PATCH 09/25] join support condition (#2201) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2201 --- engine.go | 16 ++++++++-------- integrations/session_find_test.go | 5 +++++ interface.go | 2 +- internal/statements/join.go | 30 ++++++++++++++++++++++++------ session.go | 2 +- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/engine.go b/engine.go index 81cfc7a9..a42519ee 100644 --- a/engine.go +++ b/engine.go @@ -330,7 +330,7 @@ func (engine *Engine) Ping() error { // SQL method let's you manually write raw SQL and operate // For example: // -// engine.SQL("select * from user").Find(&users) +// engine.SQL("select * from user").Find(&users) // // This code will execute "select * from user" and set the records to users func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session { @@ -996,9 +996,8 @@ func (engine *Engine) Desc(colNames ...string) *Session { // Asc will generate "ORDER BY column1,column2 Asc" // This method can chainable use. // -// engine.Desc("name").Asc("age").Find(&users) -// // SELECT * FROM user ORDER BY name DESC, age ASC -// +// engine.Desc("name").Asc("age").Find(&users) +// // SELECT * FROM user ORDER BY name DESC, age ASC func (engine *Engine) Asc(colNames ...string) *Session { session := engine.NewSession() session.isAutoClose = true @@ -1020,7 +1019,7 @@ func (engine *Engine) Prepare() *Session { } // Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { +func (engine *Engine) Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session { session := engine.NewSession() session.isAutoClose = true return session.Join(joinOperator, tablename, condition, args...) @@ -1220,9 +1219,10 @@ func (engine *Engine) InsertOne(bean interface{}) (int64, error) { // Update records, bean's non-empty fields are updated contents, // condiBean' non-empty filds are conditions // CAUTION: -// 1.bool will defaultly be updated content nor conditions -// You should call UseBool if you have bool to use. -// 2.float32 & float64 may be not inexact as conditions +// +// 1.bool will defaultly be updated content nor conditions +// You should call UseBool if you have bool to use. +// 2.float32 & float64 may be not inexact as conditions func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64, error) { session := engine.NewSession() defer session.Close() diff --git a/integrations/session_find_test.go b/integrations/session_find_test.go index 6701b1b5..5c2a4c68 100644 --- a/integrations/session_find_test.go +++ b/integrations/session_find_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "xorm.io/builder" "xorm.io/xorm" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" @@ -965,6 +966,10 @@ func TestFindJoin(t *testing.T) { scenes = make([]SceneItem, 0) err = testEngine.Join("INNER", "order", "`scene_item`.`device_id`=`order`.`id`").Find(&scenes) assert.NoError(t, err) + + scenes = make([]SceneItem, 0) + err = testEngine.Join("INNER", "order", builder.Expr("`scene_item`.`device_id`=`order`.`id`")).Find(&scenes) + assert.NoError(t, err) } func TestJoinFindLimit(t *testing.T) { diff --git a/interface.go b/interface.go index 55ffebe4..6ad0577a 100644 --- a/interface.go +++ b/interface.go @@ -52,7 +52,7 @@ type Interface interface { NoAutoCondition(...bool) *Session NotIn(string, ...interface{}) *Session Nullable(...string) *Session - Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session + Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session Omit(columns ...string) *Session OrderBy(order interface{}, args ...interface{}) *Session Ping() error diff --git a/internal/statements/join.go b/internal/statements/join.go index 45fc2441..adf349e7 100644 --- a/internal/statements/join.go +++ b/internal/statements/join.go @@ -15,7 +15,7 @@ import ( ) // Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement { +func (statement *Statement) Join(joinOP string, tablename interface{}, condition interface{}, args ...interface{}) *Statement { var buf strings.Builder if len(statement.JoinStr) > 0 { fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP) @@ -23,6 +23,23 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition fmt.Fprintf(&buf, "%v JOIN ", joinOP) } + condStr := "" + condArgs := []interface{}{} + switch condTp := condition.(type) { + case string: + condStr = condTp + case builder.Cond: + var err error + condStr, condArgs, err = builder.ToSQL(condTp) + if err != nil { + statement.LastError = err + return statement + } + default: + statement.LastError = fmt.Errorf("unsupported join condition type: %v", condTp) + return statement + } + switch tp := tablename.(type) { case builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() @@ -35,8 +52,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) aliasName = schemas.CommonQuoter.Trim(aliasName) - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condStr)) + statement.joinArgs = append(append(statement.joinArgs, subQueryArgs...), condArgs...) case *builder.Builder: subSQL, subQueryArgs, err := tp.ToSQL() if err != nil { @@ -48,8 +65,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1]) aliasName = schemas.CommonQuoter.Trim(aliasName) - fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condition)) - statement.joinArgs = append(statement.joinArgs, subQueryArgs...) + fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), statement.quote(aliasName), statement.ReplaceQuote(condStr)) + statement.joinArgs = append(append(statement.joinArgs, subQueryArgs...), condArgs...) default: tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true) if !utils.IsSubQuery(tbName) { @@ -59,7 +76,8 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition } else { tbName = statement.ReplaceQuote(tbName) } - fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition)) + fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condStr)) + statement.joinArgs = append(statement.joinArgs, condArgs...) } statement.JoinStr = buf.String() diff --git a/session.go b/session.go index 854a125a..e1a16e5b 100644 --- a/session.go +++ b/session.go @@ -330,7 +330,7 @@ func (session *Session) NoCache() *Session { } // Join join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN -func (session *Session) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { +func (session *Session) Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session { session.statement.Join(joinOperator, tablename, condition, args...) return session } From 71270edfcc768f6344cd4b408f7a27c071197d85 Mon Sep 17 00:00:00 2001 From: Dmitry Narizhnykh Date: Mon, 12 Dec 2022 18:35:40 +0800 Subject: [PATCH 10/25] add delimiters between COMMENT ON COLUMN... for Postgres (#2156) Fix bug: CreateTableSQL func generates invalid SQL without ";" delimiters. Reviewed-on: https://gitea.com/xorm/xorm/pulls/2156 Reviewed-by: Lunny Xiao Co-authored-by: Dmitry Narizhnykh Co-committed-by: Dmitry Narizhnykh --- dialects/postgres.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dialects/postgres.go b/dialects/postgres.go index f9de5859..5efe54f4 100644 --- a/dialects/postgres.go +++ b/dialects/postgres.go @@ -1343,14 +1343,14 @@ func (db *postgres) CreateTableSQL(ctx context.Context, queryer core.Queryer, ta commentSQL := "; " if table.Comment != "" { // support schema.table -> "schema"."table" - commentSQL += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'", quoter.Quote(tableName), table.Comment) + commentSQL += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'; ", quoter.Quote(tableName), table.Comment) } for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if len(col.Comment) > 0 { - commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) + commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'; ", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment) } } From 5fafa00043917e1253ebac95b5de29e2f08a569f Mon Sep 17 00:00:00 2001 From: tamalsaha Date: Mon, 9 Jan 2023 13:19:29 +0800 Subject: [PATCH 11/25] fix: Correctly parse jsonb column tag (#2206) Signed-off-by: Tamal Saha Co-authored-by: Tamal Saha Reviewed-on: https://gitea.com/xorm/xorm/pulls/2206 Reviewed-by: Lunny Xiao Co-authored-by: tamalsaha Co-committed-by: tamalsaha --- tags/parser_test.go | 23 +++++++++++++++++++++++ tags/tag.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tags/parser_test.go b/tags/parser_test.go index 83c81a1e..434cfc07 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -557,6 +557,29 @@ func TestParseWithJSON(t *testing.T) { assert.True(t, table.Columns()[0].IsJSON) } +func TestParseWithJSONB(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("postgres"), + names.GonicMapper{ + "JSONB": true, + }, + names.SnakeMapper{}, + caches.NewManager(), + ) + + type StructWithJSONB struct { + Default1 []string `db:"jsonb"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithJSONB))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_jsonb", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "default1", table.Columns()[0].Name) + assert.True(t, table.Columns()[0].IsJSON) +} + func TestParseWithSQLType(t *testing.T) { parser := NewParser( "db", diff --git a/tags/tag.go b/tags/tag.go index 55f5f4cf..41d525e1 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -285,7 +285,7 @@ func CommentTagHandler(ctx *Context) error { // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *Context) error { ctx.col.SQLType = schemas.SQLType{Name: ctx.tagUname} - if ctx.tagUname == "JSON" { + if ctx.tagUname == "JSON" || ctx.tagUname == "JSONB" { ctx.col.IsJSON = true } if len(ctx.params) == 0 { From 7dc2a188761ac198bb424b98aa1fc22892798684 Mon Sep 17 00:00:00 2001 From: datbeohbbh Date: Fri, 3 Feb 2023 17:24:16 +0800 Subject: [PATCH 12/25] Bug fix: `Rows` must be closed after used (#2214) Issue: [#2213](https://gitea.com/xorm/xorm/issues/2213#issue-132724) Co-authored-by: datbeohbbh Reviewed-on: https://gitea.com/xorm/xorm/pulls/2214 Reviewed-by: Lunny Xiao Co-authored-by: datbeohbbh Co-committed-by: datbeohbbh --- engine.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine.go b/engine.go index a42519ee..f10e30d3 100644 --- a/engine.go +++ b/engine.go @@ -815,6 +815,9 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w return err } } + // !datbeohbbh! if no error, manually close + rows.Close() + sess.Close() } return nil } From 0c9963c6379477764ab4adbab74195f92d3b89dc Mon Sep 17 00:00:00 2001 From: jamlacey Date: Sat, 4 Feb 2023 21:24:29 +0800 Subject: [PATCH 13/25] Add support for go-ora driver (#2215) Co-authored-by: James Lacey Reviewed-on: https://gitea.com/xorm/xorm/pulls/2215 Reviewed-by: Lunny Xiao Co-authored-by: jamlacey Co-committed-by: jamlacey --- README.md | 1 + dialects/dialect.go | 1 + dialects/oracle.go | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/README.md b/README.md index ccf49348..f30449a1 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Drivers for Go's sql package which currently support database/sql includes: * Oracle - [github.com/godror/godror](https://github.com/godror/godror) (experiment) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) + - [github.com/sijms/go-ora](https://github.com/sijms/go-ora) (experiment) ## Installation diff --git a/dialects/dialect.go b/dialects/dialect.go index 555d96c6..70d599e6 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -290,6 +290,7 @@ func regDrvsNDialects() bool { "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, "godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }}, + "oracle": {"oracle", func() Driver { return &oracleDriver{} }, func() Dialect { return &oracle{} }}, } for driverName, v := range providedDrvsNDialects { diff --git a/dialects/oracle.go b/dialects/oracle.go index ce91cd5d..72c26ce2 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -939,3 +939,7 @@ func (o *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { } return db, nil } + +type oracleDriver struct { + godrorDriver +} From 52855dae32d7896bd0a12007d153168b77bb52db Mon Sep 17 00:00:00 2001 From: datbeohbbh Date: Sun, 12 Feb 2023 11:16:53 +0800 Subject: [PATCH 14/25] update go version to v1.17 in .drone.yml (#2219) issue: #2218 Co-authored-by: datbeohbbh Reviewed-on: https://gitea.com/xorm/xorm/pulls/2219 Reviewed-by: Lunny Xiao Co-authored-by: datbeohbbh Co-committed-by: datbeohbbh --- .drone.yml | 32 ++-- doc.go | 251 ++++++++++++++-------------- integrations/engine_dm_test.go | 1 + integrations/session_insert_test.go | 3 +- internal/json/gojson.go | 1 + internal/json/jsoniter.go | 1 + schemas/quote.go | 23 +-- session_update.go | 7 +- 8 files changed, 162 insertions(+), 157 deletions(-) diff --git a/.drone.yml b/.drone.yml index 210572b0..2bad4b5a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ trigger: - refs/pull/*/head steps: - name: test-vet - image: golang:1.15 + image: golang:1.17 pull: always volumes: - name: cache @@ -19,7 +19,7 @@ steps: commands: - make vet - name: test-sqlite3 - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -31,7 +31,7 @@ steps: - make test-sqlite3 - TEST_CACHE_ENABLE=true make test-sqlite3 - name: test-sqlite - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -41,7 +41,7 @@ steps: - make test-sqlite - TEST_QUOTE_POLICY=reserved make test-sqlite - name: test-mysql - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -58,7 +58,7 @@ steps: - TEST_CACHE_ENABLE=true make test-mysql - name: test-mysql-utf8mb4 - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -98,7 +98,7 @@ trigger: - refs/pull/*/head steps: - name: test-mysql8 - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -136,7 +136,7 @@ trigger: - refs/pull/*/head steps: - name: test-mariadb - image: golang:1.15 + image: golang:1.17 pull: never volumes: - name: cache @@ -175,7 +175,7 @@ trigger: steps: - name: test-postgres pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -190,7 +190,7 @@ steps: - name: test-postgres-schema pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -207,7 +207,7 @@ steps: - name: test-pgx pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -225,7 +225,7 @@ steps: - name: test-pgx-schema pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -267,7 +267,7 @@ trigger: steps: - name: test-mssql pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -306,7 +306,7 @@ trigger: steps: - name: test-tidb pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -339,7 +339,7 @@ trigger: steps: - name: test-cockroach pull: never - image: golang:1.15 + image: golang:1.17 volumes: - name: cache path: /go/pkg/mod @@ -375,7 +375,7 @@ services: # steps: # - name: test-dameng # pull: never -# image: golang:1.15 +# image: golang:1.17 # volumes: # - name: cache # path: /go/pkg/mod @@ -416,7 +416,7 @@ trigger: - refs/pull/*/head steps: - name: merge_coverage - image: golang:1.15 + image: golang:1.17 commands: - make coverage diff --git a/doc.go b/doc.go index a1565806..f88f5371 100644 --- a/doc.go +++ b/doc.go @@ -3,247 +3,246 @@ // license that can be found in the LICENSE file. /* - Package xorm is a simple and powerful ORM for Go. -Installation +# Installation Make sure you have installed Go 1.11+ and then: - go get xorm.io/xorm + go get xorm.io/xorm -Create Engine +# Create Engine Firstly, we should create an engine for a database - engine, err := xorm.NewEngine(driverName, dataSourceName) + engine, err := xorm.NewEngine(driverName, dataSourceName) Method NewEngine's parameters are the same as sql.Open which depend drivers' implementation. Generally, one engine for an application is enough. You can define it as a package variable. -Raw Methods +# Raw Methods XORM supports raw SQL execution: 1. query with a SQL string, the returned results is []map[string][]byte - results, err := engine.Query("select * from user") + results, err := engine.Query("select * from user") 2. query with a SQL string, the returned results is []map[string]string - results, err := engine.QueryString("select * from user") + results, err := engine.QueryString("select * from user") 3. query with a SQL string, the returned results is []map[string]interface{} - results, err := engine.QueryInterface("select * from user") + results, err := engine.QueryInterface("select * from user") 4. execute with a SQL string, the returned results - affected, err := engine.Exec("update user set .... where ...") + affected, err := engine.Exec("update user set .... where ...") -ORM Methods +# ORM Methods There are 8 major ORM methods and many helpful methods to use to operate database. 1. Insert one or multiple records to database - affected, err := engine.Insert(&struct) - // INSERT INTO struct () values () - affected, err := engine.Insert(&struct1, &struct2) - // INSERT INTO struct1 () values () - // INSERT INTO struct2 () values () - affected, err := engine.Insert(&sliceOfStruct) - // INSERT INTO struct () values (),(),() - affected, err := engine.Insert(&struct1, &sliceOfStruct2) - // INSERT INTO struct1 () values () - // INSERT INTO struct2 () values (),(),() + affected, err := engine.Insert(&struct) + // INSERT INTO struct () values () + affected, err := engine.Insert(&struct1, &struct2) + // INSERT INTO struct1 () values () + // INSERT INTO struct2 () values () + affected, err := engine.Insert(&sliceOfStruct) + // INSERT INTO struct () values (),(),() + affected, err := engine.Insert(&struct1, &sliceOfStruct2) + // INSERT INTO struct1 () values () + // INSERT INTO struct2 () values (),(),() 2. Query one record or one variable from database - has, err := engine.Get(&user) - // SELECT * FROM user LIMIT 1 + has, err := engine.Get(&user) + // SELECT * FROM user LIMIT 1 - var id int64 - has, err := engine.Table("user").Where("name = ?", name).Get(&id) - // SELECT id FROM user WHERE name = ? LIMIT 1 + var id int64 + has, err := engine.Table("user").Where("name = ?", name).Get(&id) + // SELECT id FROM user WHERE name = ? LIMIT 1 - var id int64 - var name string - has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) - // SELECT id, name FROM user LIMIT 1 + var id int64 + var name string + has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name) + // SELECT id, name FROM user LIMIT 1 3. Query multiple records from database - var sliceOfStructs []Struct - err := engine.Find(&sliceOfStructs) - // SELECT * FROM user + var sliceOfStructs []Struct + err := engine.Find(&sliceOfStructs) + // SELECT * FROM user - var mapOfStructs = make(map[int64]Struct) - err := engine.Find(&mapOfStructs) - // SELECT * FROM user + var mapOfStructs = make(map[int64]Struct) + err := engine.Find(&mapOfStructs) + // SELECT * FROM user - var int64s []int64 - err := engine.Table("user").Cols("id").Find(&int64s) - // SELECT id FROM user + var int64s []int64 + err := engine.Table("user").Cols("id").Find(&int64s) + // SELECT id FROM user 4. Query multiple records and record by record handle, there two methods, one is Iterate, another is Rows - err := engine.Iterate(new(User), func(i int, bean interface{}) error { - // do something - }) - // SELECT * FROM user + err := engine.Iterate(new(User), func(i int, bean interface{}) error { + // do something + }) + // SELECT * FROM user - rows, err := engine.Rows(...) - // SELECT * FROM user - defer rows.Close() - bean := new(Struct) - for rows.Next() { - err = rows.Scan(bean) - } + rows, err := engine.Rows(...) + // SELECT * FROM user + defer rows.Close() + bean := new(Struct) + for rows.Next() { + err = rows.Scan(bean) + } or - rows, err := engine.Cols("name", "age").Rows(...) - // SELECT * FROM user - defer rows.Close() - for rows.Next() { - var name string - var age int - err = rows.Scan(&name, &age) - } + rows, err := engine.Cols("name", "age").Rows(...) + // SELECT * FROM user + defer rows.Close() + for rows.Next() { + var name string + var age int + err = rows.Scan(&name, &age) + } 5. Update one or more records - affected, err := engine.ID(...).Update(&user) - // UPDATE user SET ... + affected, err := engine.ID(...).Update(&user) + // UPDATE user SET ... 6. Delete one or more records, Delete MUST has condition - affected, err := engine.Where(...).Delete(&user) - // DELETE FROM user Where ... + affected, err := engine.Where(...).Delete(&user) + // DELETE FROM user Where ... 7. Count records - counts, err := engine.Count(&user) - // SELECT count(*) AS total FROM user + counts, err := engine.Count(&user) + // SELECT count(*) AS total FROM user - counts, err := engine.SQL("select count(*) FROM user").Count() - // select count(*) FROM user + counts, err := engine.SQL("select count(*) FROM user").Count() + // select count(*) FROM user 8. Sum records - sumFloat64, err := engine.Sum(&user, "id") - // SELECT sum(id) from user + sumFloat64, err := engine.Sum(&user, "id") + // SELECT sum(id) from user - sumFloat64s, err := engine.Sums(&user, "id1", "id2") - // SELECT sum(id1), sum(id2) from user + sumFloat64s, err := engine.Sums(&user, "id1", "id2") + // SELECT sum(id1), sum(id2) from user - sumInt64s, err := engine.SumsInt(&user, "id1", "id2") - // SELECT sum(id1), sum(id2) from user + sumInt64s, err := engine.SumsInt(&user, "id1", "id2") + // SELECT sum(id1), sum(id2) from user -Conditions +# Conditions The above 8 methods could use with condition methods chainable. Notice: the above 8 methods should be the last chainable method. 1. ID, In - engine.ID(1).Get(&user) // for single primary key - // SELECT * FROM user WHERE id = 1 - engine.ID(schemas.PK{1, 2}).Get(&user) // for composite primary keys - // SELECT * FROM user WHERE id1 = 1 AND id2 = 2 - engine.In("id", 1, 2, 3).Find(&users) - // SELECT * FROM user WHERE id IN (1, 2, 3) - engine.In("id", []int{1, 2, 3}).Find(&users) - // SELECT * FROM user WHERE id IN (1, 2, 3) + engine.ID(1).Get(&user) // for single primary key + // SELECT * FROM user WHERE id = 1 + engine.ID(schemas.PK{1, 2}).Get(&user) // for composite primary keys + // SELECT * FROM user WHERE id1 = 1 AND id2 = 2 + engine.In("id", 1, 2, 3).Find(&users) + // SELECT * FROM user WHERE id IN (1, 2, 3) + engine.In("id", []int{1, 2, 3}).Find(&users) + // SELECT * FROM user WHERE id IN (1, 2, 3) 2. Where, And, Or - engine.Where().And().Or().Find() - // SELECT * FROM user WHERE (.. AND ..) OR ... + engine.Where().And().Or().Find() + // SELECT * FROM user WHERE (.. AND ..) OR ... 3. OrderBy, Asc, Desc - engine.Asc().Desc().Find() - // SELECT * FROM user ORDER BY .. ASC, .. DESC - engine.OrderBy().Find() - // SELECT * FROM user ORDER BY .. + engine.Asc().Desc().Find() + // SELECT * FROM user ORDER BY .. ASC, .. DESC + engine.OrderBy().Find() + // SELECT * FROM user ORDER BY .. 4. Limit, Top - engine.Limit().Find() - // SELECT * FROM user LIMIT .. OFFSET .. - engine.Top(5).Find() - // SELECT TOP 5 * FROM user // for mssql - // SELECT * FROM user LIMIT .. OFFSET 0 //for other databases + engine.Limit().Find() + // SELECT * FROM user LIMIT .. OFFSET .. + engine.Top(5).Find() + // SELECT TOP 5 * FROM user // for mssql + // SELECT * FROM user LIMIT .. OFFSET 0 //for other databases 5. SQL, let you custom SQL - var users []User - engine.SQL("select * from user").Find(&users) + var users []User + engine.SQL("select * from user").Find(&users) 6. Cols, Omit, Distinct - var users []*User - engine.Cols("col1, col2").Find(&users) - // SELECT col1, col2 FROM user - engine.Cols("col1", "col2").Where().Update(user) - // UPDATE user set col1 = ?, col2 = ? Where ... - engine.Omit("col1").Find(&users) - // SELECT col2, col3 FROM user - engine.Omit("col1").Insert(&user) - // INSERT INTO table (non-col1) VALUES () - engine.Distinct("col1").Find(&users) - // SELECT DISTINCT col1 FROM user + var users []*User + engine.Cols("col1, col2").Find(&users) + // SELECT col1, col2 FROM user + engine.Cols("col1", "col2").Where().Update(user) + // UPDATE user set col1 = ?, col2 = ? Where ... + engine.Omit("col1").Find(&users) + // SELECT col2, col3 FROM user + engine.Omit("col1").Insert(&user) + // INSERT INTO table (non-col1) VALUES () + engine.Distinct("col1").Find(&users) + // SELECT DISTINCT col1 FROM user 7. Join, GroupBy, Having - engine.GroupBy("name").Having("name='xlw'").Find(&users) - //SELECT * FROM user GROUP BY name HAVING name='xlw' - engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find(&users) - //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id + engine.GroupBy("name").Having("name='xlw'").Find(&users) + //SELECT * FROM user GROUP BY name HAVING name='xlw' + engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find(&users) + //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id -Builder +# Builder xorm could work with xorm.io/builder directly. 1. With Where - var cond = builder.Eq{"a":1, "b":2} - engine.Where(cond).Find(&users) + var cond = builder.Eq{"a":1, "b":2} + engine.Where(cond).Find(&users) 2. With In - var subQuery = builder.Select("name").From("group") - engine.In("group_name", subQuery).Find(&users) + var subQuery = builder.Select("name").From("group") + engine.In("group_name", subQuery).Find(&users) 3. With Join - var subQuery = builder.Select("name").From("group") - engine.Join("INNER", subQuery, "group.id = user.group_id").Find(&users) + var subQuery = builder.Select("name").From("group") + engine.Join("INNER", subQuery, "group.id = user.group_id").Find(&users) 4. With SetExprs - var subQuery = builder.Select("name").From("group") - engine.ID(1).SetExprs("name", subQuery).Update(new(User)) + var subQuery = builder.Select("name").From("group") + engine.ID(1).SetExprs("name", subQuery).Update(new(User)) 5. With SQL - var query = builder.Select("name").From("group") - results, err := engine.SQL(query).Find(&groups) + var query = builder.Select("name").From("group") + results, err := engine.SQL(query).Find(&groups) 6. With Query - var query = builder.Select("name").From("group") - results, err := engine.Query(query) - results, err := engine.QueryString(query) - results, err := engine.QueryInterface(query) + var query = builder.Select("name").From("group") + results, err := engine.Query(query) + results, err := engine.QueryString(query) + results, err := engine.QueryInterface(query) 7. With Exec - var query = builder.Insert("a, b").Into("table1").Select("b, c").From("table2") - results, err := engine.Exec(query) + var query = builder.Insert("a, b").Into("table1").Select("b, c").From("table2") + results, err := engine.Exec(query) More usage, please visit http://xorm.io/docs */ diff --git a/integrations/engine_dm_test.go b/integrations/engine_dm_test.go index 6c2f6103..3b195ef8 100644 --- a/integrations/engine_dm_test.go +++ b/integrations/engine_dm_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build dm // +build dm package integrations diff --git a/integrations/session_insert_test.go b/integrations/session_insert_test.go index 2495c1df..084deb38 100644 --- a/integrations/session_insert_test.go +++ b/integrations/session_insert_test.go @@ -744,7 +744,8 @@ func TestInsertMap(t *testing.T) { assert.EqualValues(t, "lunny", ims[3].Name) } -/*INSERT INTO `issue` (`repo_id`, `poster_id`, ... ,`name`, `content`, ... ,`index`) +/* +INSERT INTO `issue` (`repo_id`, `poster_id`, ... ,`name`, `content`, ... ,`index`) SELECT $1, $2, ..., $14, $15, ..., MAX(`index`) + 1 FROM `issue` WHERE `repo_id` = $1; */ func TestInsertWhere(t *testing.T) { diff --git a/internal/json/gojson.go b/internal/json/gojson.go index 4f1448e7..9bfa5c29 100644 --- a/internal/json/gojson.go +++ b/internal/json/gojson.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build gojson // +build gojson package json diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index cfe7a19e..be93ac4e 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build jsoniter // +build jsoniter package json diff --git a/schemas/quote.go b/schemas/quote.go index 4cab30fe..6df7bf0b 100644 --- a/schemas/quote.go +++ b/schemas/quote.go @@ -163,17 +163,18 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error { } // QuoteTo quotes the table or column names. i.e. if the quotes are [ and ] -// name -> [name] -// `name` -> [name] -// [name] -> [name] -// schema.name -> [schema].[name] -// `schema`.`name` -> [schema].[name] -// `schema`.name -> [schema].[name] -// schema.`name` -> [schema].[name] -// [schema].name -> [schema].[name] -// schema.[name] -> [schema].[name] -// name AS a -> [name] AS a -// schema.name AS a -> [schema].[name] AS a +// +// name -> [name] +// `name` -> [name] +// [name] -> [name] +// schema.name -> [schema].[name] +// `schema`.`name` -> [schema].[name] +// `schema`.name -> [schema].[name] +// schema.`name` -> [schema].[name] +// [schema].name -> [schema].[name] +// schema.[name] -> [schema].[name] +// name AS a -> [name] AS a +// schema.name AS a -> [schema].[name] AS a func (q Quoter) QuoteTo(buf *strings.Builder, value string) error { var i int for i < len(value) { diff --git a/session_update.go b/session_update.go index 76f311d6..e7104710 100644 --- a/session_update.go +++ b/session_update.go @@ -145,9 +145,10 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri // Update records, bean's non-empty fields are updated contents, // condiBean' non-empty filds are conditions // CAUTION: -// 1.bool will defaultly be updated content nor conditions -// You should call UseBool if you have bool to use. -// 2.float32 & float64 may be not inexact as conditions +// +// 1.bool will defaultly be updated content nor conditions +// You should call UseBool if you have bool to use. +// 2.float32 & float64 may be not inexact as conditions func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int64, error) { if session.isAutoClose { defer session.Close() From 056cecc97e9ef3f5fd216944a495c41aa98f4e4a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 20 Feb 2023 07:17:35 +0800 Subject: [PATCH 15/25] Add `Truncate` method (#2220) This PR adds a `Truncate` method which allows to delete all existing rows in a table. The current `Delete` implementation enforces conditions to prevent accidental data deletion. Co-authored-by: KN4CK3R Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2220 Reviewed-by: Lunny Xiao Co-authored-by: KN4CK3R Co-committed-by: KN4CK3R --- engine.go | 9 +++++++++ integrations/session_delete_test.go | 27 ++++++++++++++++++++++++++- interface.go | 1 + session_delete.go | 13 ++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/engine.go b/engine.go index f10e30d3..fb19176e 100644 --- a/engine.go +++ b/engine.go @@ -1233,12 +1233,21 @@ func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64 } // Delete records, bean's non-empty fields are conditions +// At least one condition must be set. func (engine *Engine) Delete(beans ...interface{}) (int64, error) { session := engine.NewSession() defer session.Close() return session.Delete(beans...) } +// Truncate records, bean's non-empty fields are conditions +// In contrast to Delete this method allows deletes without conditions. +func (engine *Engine) Truncate(beans ...interface{}) (int64, error) { + session := engine.NewSession() + defer session.Close() + return session.Truncate(beans...) +} + // Get retrieve one record from table, bean's non-empty fields // are conditions func (engine *Engine) Get(beans ...interface{}) (bool, error) { diff --git a/integrations/session_delete_test.go b/integrations/session_delete_test.go index b4e40edb..680c3215 100644 --- a/integrations/session_delete_test.go +++ b/integrations/session_delete_test.go @@ -208,7 +208,7 @@ func TestUnscopeDelete(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - var nowUnix = time.Now().Unix() + nowUnix := time.Now().Unix() var s UnscopeDeleteStruct cnt, err = testEngine.ID(1).Delete(&s) assert.NoError(t, err) @@ -266,3 +266,28 @@ func TestDelete2(t *testing.T) { assert.NoError(t, err) assert.False(t, has) } + +func TestTruncate(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + type TruncateUser struct { + Uid int64 `xorm:"id pk not null autoincr"` + } + + assert.NoError(t, testEngine.Sync(new(TruncateUser))) + + cnt, err := testEngine.Insert(&TruncateUser{}) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + _, err = testEngine.Delete(&TruncateUser{}) + assert.Error(t, err) + + _, err = testEngine.Truncate(&TruncateUser{}) + assert.NoError(t, err) + + user2 := TruncateUser{} + has, err := testEngine.ID(1).Get(&user2) + assert.NoError(t, err) + assert.False(t, has) +} diff --git a/interface.go b/interface.go index 6ad0577a..d10abe9e 100644 --- a/interface.go +++ b/interface.go @@ -31,6 +31,7 @@ type Interface interface { Decr(column string, arg ...interface{}) *Session Desc(...string) *Session Delete(...interface{}) (int64, error) + Truncate(...interface{}) (int64, error) Distinct(columns ...string) *Session DropIndexes(bean interface{}) error Exec(sqlOrArgs ...interface{}) (sql.Result, error) diff --git a/session_delete.go b/session_delete.go index 322d5a44..d36b9e52 100644 --- a/session_delete.go +++ b/session_delete.go @@ -91,7 +91,18 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri } // Delete records, bean's non-empty fields are conditions +// At least one condition must be set. func (session *Session) Delete(beans ...interface{}) (int64, error) { + return session.delete(beans, true) +} + +// Truncate records, bean's non-empty fields are conditions +// In contrast to Delete this method allows deletes without conditions. +func (session *Session) Truncate(beans ...interface{}) (int64, error) { + return session.delete(beans, false) +} + +func (session *Session) delete(beans []interface{}, mustHaveConditions bool) (int64, error) { if session.isAutoClose { defer session.Close() } @@ -127,7 +138,7 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) { } pLimitN := session.statement.LimitN - if condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) { + if mustHaveConditions && condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) { return 0, ErrNeedDeletedCond } From 914f2db9eaf24184a34499e8ba8a705202a49c65 Mon Sep 17 00:00:00 2001 From: fanshengshuai Date: Tue, 28 Feb 2023 23:42:42 +0800 Subject: [PATCH 16/25] =?UTF-8?q?mysql=E5=AD=97=E6=AE=B5=E4=B8=BAUNSIGNED?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E4=BC=9A=E5=AF=BC=E8=87=B4=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=97=A0=E6=B3=95=E8=AF=86=E5=88=AB=EF=BC=8C?= =?UTF-8?q?=E8=BF=94=E5=9B=9ERawBytes=EF=BC=8CJSON=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E4=B8=BAString=20(#2225)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 如题 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2225 Reviewed-by: Lunny Xiao Co-authored-by: fanshengshuai Co-committed-by: fanshengshuai --- dialects/mysql.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dialects/mysql.go b/dialects/mysql.go index 6ed4a1be..195e1f23 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -738,8 +738,9 @@ func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { } func (p *mysqlDriver) GenScanResult(colType string) (interface{}, error) { + colType = strings.Replace(colType, "UNSIGNED ", "", -1) switch colType { - case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET": + case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET", "JSON": var s sql.NullString return &s, nil case "BIGINT": From d485abba57930d6eacd607405a84c6862886102f Mon Sep 17 00:00:00 2001 From: datbeohbbh Date: Mon, 6 Mar 2023 18:55:33 +0800 Subject: [PATCH 17/25] add `(*Engine) SetConnMaxIdleTime` (#2229) issue: https://gitea.com/xorm/xorm/issues/2228 Co-authored-by: datbeohbbh Reviewed-on: https://gitea.com/xorm/xorm/pulls/2229 Reviewed-by: Lunny Xiao Co-authored-by: datbeohbbh Co-committed-by: datbeohbbh --- engine.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine.go b/engine.go index fb19176e..389819e7 100644 --- a/engine.go +++ b/engine.go @@ -254,6 +254,11 @@ func (engine *Engine) SetConnMaxLifetime(d time.Duration) { engine.DB().SetConnMaxLifetime(d) } +// SetConnMaxIdleTime sets the maximum amount of time a connection may be idle. +func (engine *Engine) SetConnMaxIdleTime(d time.Duration) { + engine.DB().SetConnMaxIdleTime(d) +} + // SetMaxOpenConns is only available for go 1.2+ func (engine *Engine) SetMaxOpenConns(conns int) { engine.DB().SetMaxOpenConns(conns) From 94fcec7f65f87b4a227f2b45b6947247a6f0208d Mon Sep 17 00:00:00 2001 From: Alberto Garcia Date: Thu, 6 Apr 2023 21:47:59 +0800 Subject: [PATCH 18/25] parse timestamp with milliseconds in tz format (#2246) This pr fixes a bug caused when a timestmap in TZ format with milliseconds (`2023-04-05T15:50:48.256816Z` or `2023-04-05T15:50:48.256816+08:00`) is being parsed. - The bug is happens in the function `String2Time` in `convert/time.go` - if the timestamp contains milliseconds a layout with the structure `2006-01-02 15:04:05.` is used to parse it - This layout contains an space between the date and the hour, if the timestamp returned by the db contains a `T` the parser fails and an error is returned This pr adds a check for the `T` and milliseconds in the timestamp, if both conditions are present the date is parsed using the RFC3339 layout Added test cases to check timestamps in this format Solves [#2244](https://gitea.com/xorm/xorm/issues/2244) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2246 Reviewed-by: Lunny Xiao Co-authored-by: Alberto Garcia Co-committed-by: Alberto Garcia --- convert/time.go | 7 +++++++ convert/time_test.go | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/convert/time.go b/convert/time.go index cc2e0a10..dab6a9a2 100644 --- a/convert/time.go +++ b/convert/time.go @@ -40,6 +40,13 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t } dt = dt.In(convertedLocation) return &dt, nil + } else if len(s) >= 21 && s[10] == 'T' && s[19] == '.' { + dt, err := time.Parse(time.RFC3339, s) + if err != nil { + return nil, err + } + dt = dt.In(convertedLocation) + return &dt, nil } else if len(s) >= 21 && s[19] == '.' { var layout = "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20) dt, err := time.ParseInLocation(layout, s, originalLocation) diff --git a/convert/time_test.go b/convert/time_test.go index 5ddceb64..4b1c2279 100644 --- a/convert/time_test.go +++ b/convert/time_test.go @@ -16,10 +16,19 @@ func TestString2Time(t *testing.T) { assert.NoError(t, err) var kases = map[string]time.Time{ - "2021-08-10": time.Date(2021, 8, 10, 8, 0, 0, 0, expectedLoc), - "2021-06-06T22:58:20+08:00": time.Date(2021, 6, 6, 22, 58, 20, 0, expectedLoc), - "2021-07-11 10:44:00": time.Date(2021, 7, 11, 18, 44, 0, 0, expectedLoc), - "2021-08-10T10:33:04Z": time.Date(2021, 8, 10, 18, 33, 04, 0, expectedLoc), + "2021-08-10": time.Date(2021, 8, 10, 8, 0, 0, 0, expectedLoc), + "2021-07-11 10:44:00": time.Date(2021, 7, 11, 18, 44, 0, 0, expectedLoc), + "2021-07-11 10:44:00.999": time.Date(2021, 7, 11, 18, 44, 0, 999000000, expectedLoc), + "2021-07-11 10:44:00.999999": time.Date(2021, 7, 11, 18, 44, 0, 999999000, expectedLoc), + "2021-07-11 10:44:00.999999999": time.Date(2021, 7, 11, 18, 44, 0, 999999999, expectedLoc), + "2021-06-06T22:58:20+08:00": time.Date(2021, 6, 6, 22, 58, 20, 0, expectedLoc), + "2021-06-06T22:58:20.999+08:00": time.Date(2021, 6, 6, 22, 58, 20, 999000000, expectedLoc), + "2021-06-06T22:58:20.999999+08:00": time.Date(2021, 6, 6, 22, 58, 20, 999999000, expectedLoc), + "2021-06-06T22:58:20.999999999+08:00": time.Date(2021, 6, 6, 22, 58, 20, 999999999, expectedLoc), + "2021-08-10T10:33:04Z": time.Date(2021, 8, 10, 18, 33, 04, 0, expectedLoc), + "2021-08-10T10:33:04.999Z": time.Date(2021, 8, 10, 18, 33, 04, 999000000, expectedLoc), + "2021-08-10T10:33:04.999999Z": time.Date(2021, 8, 10, 18, 33, 04, 999999000, expectedLoc), + "2021-08-10T10:33:04.999999999Z": time.Date(2021, 8, 10, 18, 33, 04, 999999999, expectedLoc), } for layout, tm := range kases { t.Run(layout, func(t *testing.T) { From 23be940bad6a1ca680838b35b6cc9031728e1710 Mon Sep 17 00:00:00 2001 From: Abei1uo <2676567028@qq.com> Date: Fri, 12 May 2023 14:58:38 +0800 Subject: [PATCH 19/25] Fix session insert interface slice commit panic(#2259) (#2260) Fix session insert interface slice commit panic(#2259) -- TestInsertMulti2InterfaceTransaction is the test case Reviewed-on: https://gitea.com/xorm/xorm/pulls/2260 Co-authored-by: Abei1uo <2676567028@qq.com> Co-committed-by: Abei1uo <2676567028@qq.com> --- integrations/session_tx_test.go | 33 +++++++++++++++++++++++++++++++++ schemas/column.go | 2 ++ 2 files changed, 35 insertions(+) diff --git a/integrations/session_tx_test.go b/integrations/session_tx_test.go index 8d6519d0..890e755d 100644 --- a/integrations/session_tx_test.go +++ b/integrations/session_tx_test.go @@ -185,3 +185,36 @@ func TestMultipleTransaction(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 0, len(ms)) } + +func TestInsertMulti2InterfaceTransaction(t *testing.T) { + + type Multi2InterfaceTransaction struct { + ID uint64 `xorm:"id pk autoincr"` + Name string + Alias string + CreateTime time.Time `xorm:"created"` + UpdateTime time.Time `xorm:"updated"` + } + assert.NoError(t, PrepareEngine()) + assertSync(t, new(Multi2InterfaceTransaction)) + session := testEngine.NewSession() + defer session.Close() + err := session.Begin() + assert.NoError(t, err) + + users := []interface{}{ + &Multi2InterfaceTransaction{Name: "a", Alias: "A"}, + &Multi2InterfaceTransaction{Name: "b", Alias: "B"}, + &Multi2InterfaceTransaction{Name: "c", Alias: "C"}, + &Multi2InterfaceTransaction{Name: "d", Alias: "D"}, + } + cnt, err := session.Insert(&users) + + assert.NoError(t, err) + assert.EqualValues(t, len(users), cnt) + + assert.NotPanics(t, func() { + err = session.Commit() + assert.NoError(t, err) + }) +} diff --git a/schemas/column.go b/schemas/column.go index 001769cd..5a579e92 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -89,6 +89,8 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { v.Set(reflect.New(v.Type().Elem())) } v = v.Elem() + } else if v.Kind() == reflect.Interface { + v = reflect.Indirect(v.Elem()) } v = v.FieldByIndex([]int{i}) } From e6907e9a62cb107d43724866c3b3006e408d74e5 Mon Sep 17 00:00:00 2001 From: Martin Viggiano Date: Wed, 17 May 2023 09:04:00 +0800 Subject: [PATCH 20/25] oracle: Fix quotes on DropTableSQL function, add IsSequenceExist function. (#2265) - Fixed quotes on drop table SQL statement. - Implemented IsSequenceExists function for oracle dialect. Reviewed-on: https://gitea.com/xorm/xorm/pulls/2265 Reviewed-by: Lunny Xiao Co-authored-by: Martin Viggiano Co-committed-by: Martin Viggiano --- dialects/oracle.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dialects/oracle.go b/dialects/oracle.go index 72c26ce2..b0c5c38f 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -609,7 +609,7 @@ func (db *oracle) IsReserved(name string) bool { } func (db *oracle) DropTableSQL(tableName string) (string, bool) { - return fmt.Sprintf("DROP TABLE `%s`", tableName), false + return fmt.Sprintf("DROP TABLE \"%s\"", tableName), false } func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { @@ -645,6 +645,10 @@ func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl return sql, false, nil } +func (db *oracle) IsSequenceExist(ctx context.Context, queryer core.Queryer, seqName string) (bool, error) { + return db.HasRecords(queryer, ctx, `SELECT sequence_name FROM user_sequences WHERE sequence_name = :1`, seqName) +} + func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) { switch quotePolicy { case QuotePolicyNone: From 190384b4cdb9fe0d2c798ef98321aaaa442f4426 Mon Sep 17 00:00:00 2001 From: brookechen Date: Wed, 17 May 2023 15:20:40 +0800 Subject: [PATCH 21/25] =?UTF-8?q?AutoIncrement=E5=88=97=E5=B8=A6ID?= =?UTF-8?q?=E6=8F=92=E5=85=A5=E6=95=B0=E6=8D=AE=E6=97=B6=E6=B2=A1=E6=9C=89?= =?UTF-8?q?Commit=20(#2264)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 业务场景中,需要预留(1 ~ 100)的ID给系统规则使用。所以会先使用插入将AutoIncrement列的id偏移到一个特定的值(如:100),然后“带ID调用Insert插入系统规则”。 当带ID插入时,由于没有commit,会被rollback掉。 Reviewed-on: https://gitea.com/xorm/xorm/pulls/2264 Reviewed-by: Lunny Xiao Co-authored-by: brookechen Co-committed-by: brookechen --- session_insert.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/session_insert.go b/session_insert.go index fc025613..cfa26d39 100644 --- a/session_insert.go +++ b/session_insert.go @@ -353,15 +353,15 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) { if err != nil { return 0, err } - if needCommit { - if err := session.Commit(); err != nil { - return 0, err - } - } - if id == 0 { - return 0, errors.New("insert successfully but not returned id") + } + if needCommit { + if err := session.Commit(); err != nil { + return 0, err } } + if id == 0 { + return 0, errors.New("insert successfully but not returned id") + } defer handleAfterInsertProcessorFunc(bean) From 04d36cfa818d6bbe22ae4607726b74af58562619 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 25 May 2023 18:15:09 +0800 Subject: [PATCH 22/25] Use actions instead of drone (#2258) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2258 --- .drone.yml | 437 ---------------------------- .gitea/workflows/release-tag.yml | 23 ++ .gitea/workflows/test-cockroach.yml | 56 ++++ .gitea/workflows/test-mariadb.yml | 56 ++++ .gitea/workflows/test-mssql.yml | 56 ++++ .gitea/workflows/test-mysql.yml | 56 ++++ .gitea/workflows/test-mysql8.yml | 56 ++++ .gitea/workflows/test-postgres.yml | 79 +++++ .gitea/workflows/test-sqlite.yml | 49 ++++ .gitea/workflows/test-tidb.yml | 52 ++++ 10 files changed, 483 insertions(+), 437 deletions(-) delete mode 100644 .drone.yml create mode 100644 .gitea/workflows/release-tag.yml create mode 100644 .gitea/workflows/test-cockroach.yml create mode 100644 .gitea/workflows/test-mariadb.yml create mode 100644 .gitea/workflows/test-mssql.yml create mode 100644 .gitea/workflows/test-mysql.yml create mode 100644 .gitea/workflows/test-mysql8.yml create mode 100644 .gitea/workflows/test-postgres.yml create mode 100644 .gitea/workflows/test-sqlite.yml create mode 100644 .gitea/workflows/test-tidb.yml diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 2bad4b5a..00000000 --- a/.drone.yml +++ /dev/null @@ -1,437 +0,0 @@ ---- -kind: pipeline -name: test-mysql -environment: - GO111MODULE: "on" - GOPROXY: "https://goproxy.io" - CGO_ENABLED: 1 -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: test-vet - image: golang:1.17 - pull: always - volumes: - - name: cache - path: /go/pkg/mod - commands: - - make vet -- name: test-sqlite3 - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - depends_on: - - test-vet - commands: - - make fmt-check - - make test - - make test-sqlite3 - - TEST_CACHE_ENABLE=true make test-sqlite3 -- name: test-sqlite - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - depends_on: - - test-vet - commands: - - make test-sqlite - - TEST_QUOTE_POLICY=reserved make test-sqlite -- name: test-mysql - image: golang:1.17 - pull: never - volumes: - - name: cache - path: /go/pkg/mod - depends_on: - - test-vet - environment: - TEST_MYSQL_HOST: mysql - TEST_MYSQL_CHARSET: utf8 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - TEST_CACHE_ENABLE=true make test-mysql - -- name: test-mysql-utf8mb4 - image: golang:1.17 - pull: never - volumes: - - name: cache - path: /go/pkg/mod - depends_on: - - test-mysql - environment: - TEST_MYSQL_HOST: mysql - TEST_MYSQL_CHARSET: utf8mb4 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - make test-mysql - - TEST_QUOTE_POLICY=reserved make test-mysql-tls - -volumes: -- name: cache - host: - path: /tmp/cache - -services: -- name: mysql - image: mysql:5.7 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - ---- -kind: pipeline -name: test-mysql8 -depends_on: - - test-mysql -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: test-mysql8 - image: golang:1.17 - pull: never - volumes: - - name: cache - path: /go/pkg/mod - environment: - TEST_MYSQL_HOST: mysql8 - TEST_MYSQL_CHARSET: utf8mb4 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - make test-mysql - - TEST_CACHE_ENABLE=true make test-mysql - -volumes: -- name: cache - host: - path: /tmp/cache - -services: -- name: mysql8 - image: mysql:8.0 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - ---- -kind: pipeline -name: test-mariadb -depends_on: - - test-mysql8 -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: test-mariadb - image: golang:1.17 - pull: never - volumes: - - name: cache - path: /go/pkg/mod - environment: - TEST_MYSQL_HOST: mariadb - TEST_MYSQL_CHARSET: utf8mb4 - TEST_MYSQL_DBNAME: xorm_test - TEST_MYSQL_USERNAME: root - TEST_MYSQL_PASSWORD: - commands: - - make test-mysql - - TEST_QUOTE_POLICY=reserved make test-mysql - -volumes: -- name: cache - host: - path: /tmp/cache - -services: -- name: mariadb - image: mariadb:10.4 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - ---- -kind: pipeline -name: test-postgres -depends_on: - - test-mariadb -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: test-postgres - pull: never - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - environment: - TEST_PGSQL_HOST: pgsql - TEST_PGSQL_DBNAME: xorm_test - TEST_PGSQL_USERNAME: postgres - TEST_PGSQL_PASSWORD: postgres - commands: - - make test-postgres - - TEST_CACHE_ENABLE=true make test-postgres - -- name: test-postgres-schema - pull: never - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - depends_on: - - test-postgres - environment: - TEST_PGSQL_HOST: pgsql - TEST_PGSQL_SCHEMA: xorm - TEST_PGSQL_DBNAME: xorm_test - TEST_PGSQL_USERNAME: postgres - TEST_PGSQL_PASSWORD: postgres - commands: - - TEST_QUOTE_POLICY=reserved make test-postgres - -- name: test-pgx - pull: never - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - depends_on: - - test-postgres-schema - environment: - TEST_PGSQL_HOST: pgsql - TEST_PGSQL_DBNAME: xorm_test - TEST_PGSQL_USERNAME: postgres - TEST_PGSQL_PASSWORD: postgres - commands: - - make test-pgx - - TEST_CACHE_ENABLE=true make test-pgx - - TEST_QUOTE_POLICY=reserved make test-pgx - -- name: test-pgx-schema - pull: never - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - depends_on: - - test-pgx - environment: - TEST_PGSQL_HOST: pgsql - TEST_PGSQL_SCHEMA: xorm - TEST_PGSQL_DBNAME: xorm_test - TEST_PGSQL_USERNAME: postgres - TEST_PGSQL_PASSWORD: postgres - commands: - - make test-pgx - - TEST_CACHE_ENABLE=true make test-pgx - - TEST_QUOTE_POLICY=reserved make test-pgx - -volumes: -- name: cache - host: - path: /tmp/cache - -services: -- name: pgsql - image: postgres:9.5 - environment: - POSTGRES_DB: xorm_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ---- -kind: pipeline -name: test-mssql -depends_on: - - test-postgres -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: test-mssql - pull: never - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - environment: - TEST_MSSQL_HOST: mssql - TEST_MSSQL_DBNAME: xorm_test - TEST_MSSQL_USERNAME: sa - TEST_MSSQL_PASSWORD: "yourStrong(!)Password" - commands: - - make test-mssql - - TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql - -volumes: -- name: cache - host: - path: /tmp/cache - -services: -- name: mssql - pull: always - image: mcr.microsoft.com/mssql/server:latest - environment: - ACCEPT_EULA: Y - SA_PASSWORD: yourStrong(!)Password - MSSQL_PID: Standard - ---- -kind: pipeline -name: test-tidb -depends_on: - - test-mssql -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: test-tidb - pull: never - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - environment: - TEST_TIDB_HOST: "tidb:4000" - TEST_TIDB_DBNAME: xorm_test - TEST_TIDB_USERNAME: root - TEST_TIDB_PASSWORD: - commands: - - make test-tidb - -volumes: -- name: cache - host: - path: /tmp/cache - -services: -- name: tidb - image: pingcap/tidb:v3.0.3 - ---- -kind: pipeline -name: test-cockroach -depends_on: - - test-tidb -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: test-cockroach - pull: never - image: golang:1.17 - volumes: - - name: cache - path: /go/pkg/mod - environment: - TEST_COCKROACH_HOST: "cockroach:26257" - TEST_COCKROACH_DBNAME: xorm_test - TEST_COCKROACH_USERNAME: root - TEST_COCKROACH_PASSWORD: - commands: - - sleep 10 - - make test-cockroach - -volumes: -- name: cache - host: - path: /tmp/cache - -services: -- name: cockroach - image: cockroachdb/cockroach:v19.2.4 - commands: - - /cockroach/cockroach start --insecure - -# --- -# kind: pipeline -# name: test-dameng -# depends_on: -# - test-cockroach -# trigger: -# ref: -# - refs/heads/master -# - refs/pull/*/head -# steps: -# - name: test-dameng -# pull: never -# image: golang:1.17 -# volumes: -# - name: cache -# path: /go/pkg/mod -# environment: -# TEST_DAMENG_HOST: "dameng:5236" -# TEST_DAMENG_USERNAME: SYSDBA -# TEST_DAMENG_PASSWORD: SYSDBA -# commands: -# - sleep 30 -# - make test-dameng - -# volumes: -# - name: cache -# host: -# path: /tmp/cache - -# services: -# - name: dameng -# image: lunny/dm:v1.0 -# commands: -# - /bin/bash /startDm.sh - ---- -kind: pipeline -name: merge_coverage -depends_on: - - test-mysql - - test-mysql8 - - test-mariadb - - test-postgres - - test-mssql - - test-tidb - - test-cockroach - #- test-dameng -trigger: - ref: - - refs/heads/master - - refs/pull/*/head -steps: -- name: merge_coverage - image: golang:1.17 - commands: - - make coverage - ---- -kind: pipeline -name: release-tag -trigger: - event: - - tag -steps: -- name: release-tag-gitea - pull: always - image: plugins/gitea-release:latest - settings: - base_url: https://gitea.com - title: '${DRONE_TAG} is released' - api_key: - from_secret: gitea_token \ No newline at end of file diff --git a/.gitea/workflows/release-tag.yml b/.gitea/workflows/release-tag.yml new file mode 100644 index 00000000..10ed831e --- /dev/null +++ b/.gitea/workflows/release-tag.yml @@ -0,0 +1,23 @@ +name: release + +on: + push: + tags: + - '*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: setup go + uses: https://github.com/actions/setup-go@v4 + with: + go-version: '>=1.20.1' + - name: Use Go Action + id: use-go-action + uses: actions/release-action@main + with: + api_key: '${{secrets.RELEASE_TOKEN}}' \ No newline at end of file diff --git a/.gitea/workflows/test-cockroach.yml b/.gitea/workflows/test-cockroach.yml new file mode 100644 index 00000000..20462688 --- /dev/null +++ b/.gitea/workflows/test-cockroach.yml @@ -0,0 +1,56 @@ +name: test cockroach +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + test-cockroach: + name: test cockroach + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: test cockroach + env: + TEST_COCKROACH_HOST: "cockroach:26257" + TEST_COCKROACH_DBNAME: xorm_test + TEST_COCKROACH_USERNAME: root + TEST_COCKROACH_PASSWORD: + run: sleep 20 && make test-cockroach + + services: + cockroach: + image: cockroachdb/cockroach:v19.2.4 + ports: + - 26257:26257 + cmd: + - '/cockroach/cockroach' + - 'start' + - '--insecure' \ No newline at end of file diff --git a/.gitea/workflows/test-mariadb.yml b/.gitea/workflows/test-mariadb.yml new file mode 100644 index 00000000..466f3858 --- /dev/null +++ b/.gitea/workflows/test-mariadb.yml @@ -0,0 +1,56 @@ +name: test mariadb +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + lint: + name: test mariadb + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: test mariadb + env: + TEST_MYSQL_HOST: mariadb + TEST_MYSQL_CHARSET: utf8mb4 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + run: TEST_QUOTE_POLICY=reserved make test-mysql + + services: + mariadb: + image: mariadb:10.4 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + ports: + - 3306:3306 \ No newline at end of file diff --git a/.gitea/workflows/test-mssql.yml b/.gitea/workflows/test-mssql.yml new file mode 100644 index 00000000..d02e6956 --- /dev/null +++ b/.gitea/workflows/test-mssql.yml @@ -0,0 +1,56 @@ +name: test mssql +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + test-mssql: + name: test mssql + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: test mssql + env: + TEST_MSSQL_HOST: mssql + TEST_MSSQL_DBNAME: xorm_test + TEST_MSSQL_USERNAME: sa + TEST_MSSQL_PASSWORD: "yourStrong(!)Password" + run: TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql + + services: + mssql: + image: mcr.microsoft.com/mssql/server:latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: yourStrong(!)Password + MSSQL_PID: Standard + ports: + - 1433:1433 \ No newline at end of file diff --git a/.gitea/workflows/test-mysql.yml b/.gitea/workflows/test-mysql.yml new file mode 100644 index 00000000..03ee2725 --- /dev/null +++ b/.gitea/workflows/test-mysql.yml @@ -0,0 +1,56 @@ +name: test mysql +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + test-mysql: + name: test mysql + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: test mysql utf8mb4 + env: + TEST_MYSQL_HOST: mysql + TEST_MYSQL_CHARSET: utf8mb4 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + run: TEST_QUOTE_POLICY=reserved make test-mysql-tls + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + ports: + - 3306:3306 \ No newline at end of file diff --git a/.gitea/workflows/test-mysql8.yml b/.gitea/workflows/test-mysql8.yml new file mode 100644 index 00000000..3fbd7c30 --- /dev/null +++ b/.gitea/workflows/test-mysql8.yml @@ -0,0 +1,56 @@ +name: test mysql8 +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + lint: + name: test mysql8 + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: test mysql8 + env: + TEST_MYSQL_HOST: mysql8 + TEST_MYSQL_CHARSET: utf8mb4 + TEST_MYSQL_DBNAME: xorm_test + TEST_MYSQL_USERNAME: root + TEST_MYSQL_PASSWORD: + run: TEST_CACHE_ENABLE=true make test-mysql + + services: + mysql8: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: xorm_test + ports: + - 3306:3306 \ No newline at end of file diff --git a/.gitea/workflows/test-postgres.yml b/.gitea/workflows/test-postgres.yml new file mode 100644 index 00000000..89aa72c3 --- /dev/null +++ b/.gitea/workflows/test-postgres.yml @@ -0,0 +1,79 @@ +name: test postgres +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + lint: + name: test postgres + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: test postgres + env: + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + run: TEST_CACHE_ENABLE=true make test-postgres + - name: test postgres with schema + env: + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_SCHEMA: xorm + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + run: TEST_QUOTE_POLICY=reserved make test-postgres + - name: test pgx + env: + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + run: TEST_CACHE_ENABLE=true make test-pgx + - name: test pgx with schema + env: + TEST_PGSQL_HOST: pgsql + TEST_PGSQL_SCHEMA: xorm + TEST_PGSQL_DBNAME: xorm_test + TEST_PGSQL_USERNAME: postgres + TEST_PGSQL_PASSWORD: postgres + run: TEST_QUOTE_POLICY=reserved make test-pgx + + services: + pgsql: + image: postgres:9.5 + env: + POSTGRES_DB: xorm_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 \ No newline at end of file diff --git a/.gitea/workflows/test-sqlite.yml b/.gitea/workflows/test-sqlite.yml new file mode 100644 index 00000000..cca2e786 --- /dev/null +++ b/.gitea/workflows/test-sqlite.yml @@ -0,0 +1,49 @@ +name: test sqlite +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + test-sqlite: + name: unit test & test sqlite + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: vet + run: make vet + - name: format check + run: make fmt-check + - name: unit test + run: make test + - name: test sqlite3 + run: make test-sqlite3 + - name: test sqlite3 with cache + run: TEST_CACHE_ENABLE=true make test-sqlite3 \ No newline at end of file diff --git a/.gitea/workflows/test-tidb.yml b/.gitea/workflows/test-tidb.yml new file mode 100644 index 00000000..fa6e27ad --- /dev/null +++ b/.gitea/workflows/test-tidb.yml @@ -0,0 +1,52 @@ +name: test tidb +on: + push: + branches: + - master + pull_request: + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + test-tidb: + name: test tidb + runs-on: ubuntu-latest + steps: + # - name: cache go path + # id: cache-go-path + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_path + # key: go_path-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_path-${{ github.repository }}- + # go_path- + # - name: cache go cache + # id: cache-go-cache + # uses: https://github.com/actions/cache@v3 + # with: + # path: /go_cache + # key: go_cache-${{ github.repository }}-${{ github.ref_name }} + # restore-keys: | + # go_cache-${{ github.repository }}- + # go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: https://github.com/actions/checkout@v3 + - name: test tidb + env: + TEST_TIDB_HOST: "tidb:4000" + TEST_TIDB_DBNAME: xorm_test + TEST_TIDB_USERNAME: root + TEST_TIDB_PASSWORD: + run: make test-tidb + + services: + tidb: + image: pingcap/tidb:v3.0.3 + ports: + - 4000:4000 \ No newline at end of file From 57f7d69f1bbebc00dddf22308438c65034e4e457 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Mon, 29 May 2023 13:01:43 +0000 Subject: [PATCH 23/25] Fix test-cockroach workflow (#2269) The image has already specified the entrypoint so we don't need to call `/cockroach/cockroach` in `cmd`. ![image](/attachments/669496c1-5673-4609-abc7-f42846e4d479) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2269 Co-authored-by: Zettat123 Co-committed-by: Zettat123 --- .gitea/workflows/test-cockroach.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitea/workflows/test-cockroach.yml b/.gitea/workflows/test-cockroach.yml index 20462688..0ca18861 100644 --- a/.gitea/workflows/test-cockroach.yml +++ b/.gitea/workflows/test-cockroach.yml @@ -51,6 +51,5 @@ jobs: ports: - 26257:26257 cmd: - - '/cockroach/cockroach' - 'start' - - '--insecure' \ No newline at end of file + - '--insecure' From cb851a2f95864bf2f27b3e3222e5f38411928e79 Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Wed, 31 May 2023 01:43:24 +0000 Subject: [PATCH 24/25] Filter support passing context (#2200) (#2270) ```diff // Filter is an interface to filter SQL type Filter interface { --- Do(sql string) string +++ Do(ctx context.Context, sql string) string } ``` ### Adds a `Context` parameter to the `Do` method of the Filter interface. Developers can rewrite SQL through the `Filter` `Do` method and **need to get the necessary data from the Context** to assist. For example, get user information through `Context`, so that different users can use different tables. Another example is to get the flags through `Context` to add annotations to the SQL, and use the annotations to let the subsequent `DB-Proxy` to achieve read/write separation. Reviewed-on: https://gitea.com/xorm/xorm/pulls/2270 Reviewed-by: Lunny Xiao Co-authored-by: LinkinStars Co-committed-by: LinkinStars --- dialects/filter.go | 5 +++-- session_delete.go | 2 +- session_find.go | 2 +- session_get.go | 2 +- session_raw.go | 2 +- session_update.go | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/dialects/filter.go b/dialects/filter.go index bfe2e93e..ff9a44e8 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -5,13 +5,14 @@ package dialects import ( + "context" "fmt" "strings" ) // Filter is an interface to filter SQL type Filter interface { - Do(sql string) string + Do(ctx context.Context, sql string) string } // SeqFilter filter SQL replace ?, ? ... to $1, $2 ... @@ -71,6 +72,6 @@ func convertQuestionMark(sql, prefix string, start int) string { } // Do implements Filter -func (s *SeqFilter) Do(sql string) string { +func (s *SeqFilter) Do(ctx context.Context, sql string) string { return convertQuestionMark(sql, s.Prefix, s.Start) } diff --git a/session_delete.go b/session_delete.go index d36b9e52..6c63d9b0 100644 --- a/session_delete.go +++ b/session_delete.go @@ -30,7 +30,7 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri } for _, filter := range session.engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr) + sqlStr = filter.Do(session.ctx, sqlStr) } newsql := session.statement.ConvertIDSQL(sqlStr) diff --git a/session_find.go b/session_find.go index 2270454b..3341eafe 100644 --- a/session_find.go +++ b/session_find.go @@ -283,7 +283,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } for _, filter := range session.engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr) + sqlStr = filter.Do(session.ctx, sqlStr) } newsql := session.statement.ConvertIDSQL(sqlStr) diff --git a/session_get.go b/session_get.go index 9bb92a8b..96e362e9 100644 --- a/session_get.go +++ b/session_get.go @@ -278,7 +278,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } for _, filter := range session.engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr) + sqlStr = filter.Do(session.ctx, sqlStr) } newsql := session.statement.ConvertIDSQL(sqlStr) if newsql == "" { diff --git a/session_raw.go b/session_raw.go index add584d0..99f6be99 100644 --- a/session_raw.go +++ b/session_raw.go @@ -13,7 +13,7 @@ import ( func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { for _, filter := range session.engine.dialect.Filters() { - *sqlStr = filter.Do(*sqlStr) + *sqlStr = filter.Do(session.ctx, *sqlStr) } session.lastSQL = *sqlStr diff --git a/session_update.go b/session_update.go index e7104710..1f80e70f 100644 --- a/session_update.go +++ b/session_update.go @@ -34,7 +34,7 @@ func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr stri return ErrCacheFailed } for _, filter := range session.engine.dialect.Filters() { - newsql = filter.Do(newsql) + newsql = filter.Do(session.ctx, newsql) } session.engine.logger.Debugf("[cache] new sql: %v, %v", oldhead, newsql) From caa8a029c60642694f3417d12d902769031d1f45 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 2 Jun 2023 14:16:30 +0000 Subject: [PATCH 25/25] some optimzation (#2272) Reviewed-on: https://gitea.com/xorm/xorm/pulls/2272 --- dialects/dameng.go | 14 ++++++++++++-- dialects/filter.go | 7 ++++--- dialects/mysql.go | 12 +++++++++--- internal/statements/cache.go | 16 +++++++++++----- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/dialects/dameng.go b/dialects/dameng.go index 5e92ec2f..8bed7f13 100644 --- a/dialects/dameng.go +++ b/dialects/dameng.go @@ -709,7 +709,13 @@ func (db *dameng) CreateTableSQL(ctx context.Context, queryer core.Queryer, tabl return "", false, err } } - if _, err := b.WriteString(fmt.Sprintf("CONSTRAINT PK_%s PRIMARY KEY (", tableName)); err != nil { + if _, err := b.WriteString("CONSTRAINT PK_"); err != nil { + return "", false, err + } + if _, err := b.WriteString(tableName); err != nil { + return "", false, err + } + if _, err := b.WriteString(" PRIMARY KEY ("); err != nil { return "", false, err } if err := quoter.JoinWrite(&b, pkList, ","); err != nil { @@ -837,7 +843,11 @@ func addSingleQuote(name string) string { if name[0] == '\'' && name[len(name)-1] == '\'' { return name } - return fmt.Sprintf("'%s'", name) + var b strings.Builder + b.WriteRune('\'') + b.WriteString(name) + b.WriteRune('\'') + return b.String() } func (db *dameng) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { diff --git a/dialects/filter.go b/dialects/filter.go index ff9a44e8..add67c1b 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -6,7 +6,7 @@ package dialects import ( "context" - "fmt" + "strconv" "strings" ) @@ -29,10 +29,11 @@ func convertQuestionMark(sql, prefix string, start int) string { var isMaybeLineComment bool var isMaybeComment bool var isMaybeCommentEnd bool - var index = start + index := start for _, c := range sql { if !beginSingleQuote && !isLineComment && !isComment && c == '?' { - buf.WriteString(fmt.Sprintf("%s%v", prefix, index)) + buf.WriteString(prefix) + buf.WriteString(strconv.Itoa(index)) index++ } else { if isMaybeLineComment { diff --git a/dialects/mysql.go b/dialects/mysql.go index 195e1f23..b941a41b 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -381,11 +381,17 @@ func (db *mysql) IsTableExist(queryer core.Queryer, ctx context.Context, tableNa func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { quoter := db.dialect.Quoter() s, _ := ColumnString(db, col, true) - sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quoter.Quote(tableName), s) + var b strings.Builder + b.WriteString("ALTER TABLE ") + quoter.QuoteTo(&b, tableName) + b.WriteString(" ADD ") + b.WriteString(s) if len(col.Comment) > 0 { - sql += " COMMENT '" + col.Comment + "'" + b.WriteString(" COMMENT '") + b.WriteString(col.Comment) + b.WriteString("'") } - return sql + return b.String() } func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { diff --git a/internal/statements/cache.go b/internal/statements/cache.go index 669cd018..9dd76754 100644 --- a/internal/statements/cache.go +++ b/internal/statements/cache.go @@ -6,6 +6,7 @@ package statements import ( "fmt" + "strconv" "strings" "xorm.io/xorm/internal/utils" @@ -26,14 +27,19 @@ func (statement *Statement) ConvertIDSQL(sqlStr string) string { return "" } - var top string + var b strings.Builder + b.WriteString("SELECT ") pLimitN := statement.LimitN if pLimitN != nil && statement.dialect.URI().DBType == schemas.MSSQL { - top = fmt.Sprintf("TOP %d ", *pLimitN) + b.WriteString("TOP ") + b.WriteString(strconv.Itoa(*pLimitN)) + b.WriteString(" ") } + b.WriteString(colstrs) + b.WriteString(" FROM ") + b.WriteString(sqls[1]) - newsql := fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1]) - return newsql + return b.String() } return "" } @@ -54,7 +60,7 @@ func (statement *Statement) ConvertUpdateSQL(sqlStr string) (string, string) { return "", "" } - var whereStr = sqls[1] + whereStr := sqls[1] // TODO: for postgres only, if any other database? var paraStr string