Merge branch 'master' into telegram_webhook

This commit is contained in:
techknowlogick 2018-07-21 11:05:19 -04:00 committed by GitHub
commit b6c7ce32a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 2898 additions and 1408 deletions

10
Gopkg.lock generated
View File

@ -288,17 +288,19 @@
[[projects]]
name = "github.com/go-xorm/builder"
packages = ["."]
revision = "488224409dd8aa2ce7a5baf8d10d55764a913738"
revision = "dc8bf48f58fab2b4da338ffd25191905fd741b8f"
version = "v0.3.0"
[[projects]]
name = "github.com/go-xorm/core"
packages = ["."]
revision = "cb1d0ca71f42d3ee1bf4aba7daa16099bc31a7e9"
revision = "c10e21e7e1cec20e09398f2dfae385e58c8df555"
version = "v0.6.0"
[[projects]]
name = "github.com/go-xorm/xorm"
packages = ["."]
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
[[projects]]
branch = "master"
@ -701,6 +703,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "59451a3ad1d449f75c5e9035daf542a377c5c4a397e219bebec0aa0007ab9c39"
inputs-digest = "5ae18d543bbb8186589c003422b333097d67bb5fed8b4c294be70c012ccffc94"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -33,7 +33,7 @@ ignored = ["google.golang.org/appengine*"]
[[override]]
name = "github.com/go-xorm/xorm"
#version = "0.6.5"
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
[[override]]
name = "github.com/go-sql-driver/mysql"

View File

@ -316,6 +316,9 @@ DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for AllowCreateOrganization
; Every new user will have rights set to create organizations depending on this setting
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
; Default value for EnableDependencies
; Repositories will use depencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true
; Enable Timetracking
ENABLE_TIMETRACKING = true
; Default value for EnableTimetracking

View File

@ -182,6 +182,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha
- `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default.
## Webhook (`webhook`)

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
// register supported doc types
_ "code.gitea.io/gitea/modules/markup/csv"
_ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode"

View File

@ -477,6 +477,10 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
}
if err = issue.ChangeStatus(doer, repo, true); err != nil {
// Don't return an error when dependencies are open as this would let the push fail
if IsErrDependenciesLeft(err) {
return nil
}
return err
}
}

View File

@ -1259,3 +1259,88 @@ func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}
// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
// | |\___ \ \___ \| | /\ ___/ | ` \ ___/| |_> > ___/| | \/ /_/ \ ___/| | \ \___| \ ___/ \___ \
// |___/____ >____ >____/ \___ >_______ /\___ > __/ \___ >___| /\____ |\___ >___| /\___ >__|\___ >____ >
// \/ \/ \/ \/ \/|__| \/ \/ \/ \/ \/ \/ \/ \/
// ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyExists checks if an error is a ErrDependencyExists.
func IsErrDependencyExists(err error) bool {
_, ok := err.(ErrDependencyExists)
return ok
}
func (err ErrDependencyExists) Error() string {
return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyNotExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyNotExists checks if an error is a ErrDependencyExists.
func IsErrDependencyNotExists(err error) bool {
_, ok := err.(ErrDependencyNotExists)
return ok
}
func (err ErrDependencyNotExists) Error() string {
return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrCircularDependency represents a "DependencyCircular" kind of error.
type ErrCircularDependency struct {
IssueID int64
DependencyID int64
}
// IsErrCircularDependency checks if an error is a ErrCircularDependency.
func IsErrCircularDependency(err error) bool {
_, ok := err.(ErrCircularDependency)
return ok
}
func (err ErrCircularDependency) Error() string {
return fmt.Sprintf("circular dependencies exists (two issues blocking each other) [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependenciesLeft represents an error where the issue you're trying to close still has dependencies left.
type ErrDependenciesLeft struct {
IssueID int64
}
// IsErrDependenciesLeft checks if an error is a ErrDependenciesLeft.
func IsErrDependenciesLeft(err error) bool {
_, ok := err.(ErrDependenciesLeft)
return ok
}
func (err ErrDependenciesLeft) Error() string {
return fmt.Sprintf("issue has open dependencies [issue id: %d]", err.IssueID)
}
// ErrUnknownDependencyType represents an error where an unknown dependency type was passed
type ErrUnknownDependencyType struct {
Type DependencyType
}
// IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
func IsErrUnknownDependencyType(err error) bool {
_, ok := err.(ErrUnknownDependencyType)
return ok
}
func (err ErrUnknownDependencyType) Error() string {
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
}

View File

@ -649,6 +649,20 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository,
if issue.IsClosed == isClosed {
return nil
}
// Check for open dependencies
if isClosed && issue.Repo.IsDependenciesEnabled() {
// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
noDeps, err := IssueNoDependenciesLeft(issue)
if err != nil {
return err
}
if !noDeps {
return ErrDependenciesLeft{issue.ID}
}
}
issue.IsClosed = isClosed
if isClosed {
issue.ClosedUnix = util.TimeStampNow()
@ -1283,7 +1297,7 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) {
And("`comment`.type = ?", CommentTypeComment).
And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `comment`.poster_id").
Join("INNER", "`user`", "`user`.id = `comment`.poster_id").
Distinct("poster_id").
Find(&userIDs); err != nil {
return nil, fmt.Errorf("get poster IDs: %v", err)
@ -1598,3 +1612,33 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User)
return sess.Commit()
}
// Get Blocked By Dependencies, aka all issues this issue is blocked by.
func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_id = ?", issue.ID).
Find(&issueDeps)
}
// Get Blocking Dependencies, aka all issues this issue blocks.
func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.issue_id").
Where("dependency_id = ?", issue.ID).
Find(&issueDeps)
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
func (issue *Issue) BlockedByDependencies() ([]*Issue, error) {
return issue.getBlockedByDependencies(x)
}
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks
func (issue *Issue) BlockingDependencies() ([]*Issue, error) {
return issue.getBlockingDependencies(x)
}

View File

@ -66,6 +66,10 @@ const (
CommentTypeModifiedDeadline
// Removed a due date
CommentTypeRemovedDeadline
// Dependency added
CommentTypeAddDependency
//Dependency removed
CommentTypeRemoveDependency
)
// CommentTag defines comment tag type
@ -81,23 +85,25 @@ const (
// Comment represents a comment in commit and issue page.
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64
Label *Label `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
AssigneeID int64
RemovedAssignee bool
Assignee *User `xorm:"-"`
OldTitle string
NewTitle string
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64
Label *Label `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
AssigneeID int64
RemovedAssignee bool
Assignee *User `xorm:"-"`
OldTitle string
NewTitle string
DependentIssueID int64
DependentIssue *Issue `xorm:"-"`
CommitID int64
Line int64
@ -281,6 +287,15 @@ func (c *Comment) LoadAssigneeUser() error {
return nil
}
// LoadDepIssueDetails loads Dependent Issue Details
func (c *Comment) LoadDepIssueDetails() (err error) {
if c.DependentIssueID <= 0 || c.DependentIssue != nil {
return nil
}
c.DependentIssue, err = getIssueByID(x, c.DependentIssueID)
return err
}
// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
@ -332,22 +347,24 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
if opts.Label != nil {
LabelID = opts.Label.ID
}
comment := &Comment{
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID,
RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle,
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID,
RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle,
DependentIssueID: opts.DependentIssueID,
}
if _, err = e.Insert(comment); err != nil {
return nil, err
@ -549,6 +566,39 @@ func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, is
})
}
// Creates issue dependency comment
func createIssueDependencyComment(e *xorm.Session, doer *User, issue *Issue, dependentIssue *Issue, add bool) (err error) {
cType := CommentTypeAddDependency
if !add {
cType = CommentTypeRemoveDependency
}
// Make two comments, one in each issue
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
DependentIssueID: dependentIssue.ID,
})
if err != nil {
return
}
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: dependentIssue,
DependentIssueID: issue.ID,
})
if err != nil {
return
}
return
}
// CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct {
Type CommentType
@ -557,17 +607,18 @@ type CreateCommentOptions struct {
Issue *Issue
Label *Label
OldMilestoneID int64
MilestoneID int64
AssigneeID int64
RemovedAssignee bool
OldTitle string
NewTitle string
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
DependentIssueID int64
OldMilestoneID int64
MilestoneID int64
AssigneeID int64
RemovedAssignee bool
OldTitle string
NewTitle string
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
}
// CreateComment creates comment of issue or commit.

137
models/issue_dependency.go Normal file
View File

@ -0,0 +1,137 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// IssueDependency represents an issue dependency
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
DependencyID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
CreatedUnix util.TimeStamp `xorm:"created"`
UpdatedUnix util.TimeStamp `xorm:"updated"`
}
// DependencyType Defines Dependency Type Constants
type DependencyType int
// Define Dependency Types
const (
DependencyTypeBlockedBy DependencyType = iota
DependencyTypeBlocking
)
// CreateIssueDependency creates a new dependency for an issue
func CreateIssueDependency(user *User, issue, dep *Issue) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// Check if it aleready exists
exists, err := issueDepExists(sess, issue.ID, dep.ID)
if err != nil {
return err
}
if exists {
return ErrDependencyExists{issue.ID, dep.ID}
}
// And if it would be circular
circular, err := issueDepExists(sess, dep.ID, issue.ID)
if err != nil {
return err
}
if circular {
return ErrCircularDependency{issue.ID, dep.ID}
}
if _, err := sess.Insert(&IssueDependency{
UserID: user.ID,
IssueID: issue.ID,
DependencyID: dep.ID,
}); err != nil {
return err
}
// Add comment referencing the new dependency
if err = createIssueDependencyComment(sess, user, issue, dep, true); err != nil {
return err
}
return sess.Commit()
}
// RemoveIssueDependency removes a dependency from an issue
func RemoveIssueDependency(user *User, issue *Issue, dep *Issue, depType DependencyType) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
var issueDepToDelete IssueDependency
switch depType {
case DependencyTypeBlockedBy:
issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
case DependencyTypeBlocking:
issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
default:
return ErrUnknownDependencyType{depType}
}
affected, err := sess.Delete(&issueDepToDelete)
if err != nil {
return err
}
// If we deleted nothing, the dependency did not exist
if affected <= 0 {
return ErrDependencyNotExists{issue.ID, dep.ID}
}
// Add comment referencing the removed dependency
if err = createIssueDependencyComment(sess, user, issue, dep, false); err != nil {
return err
}
return sess.Commit()
}
// Check if the dependency already exists
func issueDepExists(e Engine, issueID int64, depID int64) (bool, error) {
return e.Where("(issue_id = ? AND dependency_id = ?)", issueID, depID).Exist(&IssueDependency{})
}
// IssueNoDependenciesLeft checks if issue can be closed
func IssueNoDependenciesLeft(issue *Issue) (bool, error) {
exists, err := x.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_dependency.issue_id = ?", issue.ID).
And("issue.is_closed = ?", "0").
Exist(&Issue{})
return !exists, err
}
// IsDependenciesEnabled returns if dependecies are enabled and returns the default setting if not set.
func (repo *Repository) IsDependenciesEnabled() bool {
var u *RepoUnit
var err error
if u, err = repo.GetUnit(UnitTypeIssues); err != nil {
log.Trace("%s", err)
return setting.Service.DefaultEnableDependencies
}
return u.IssuesConfig().EnableDependencies
}

View File

@ -0,0 +1,57 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateIssueDependency(t *testing.T) {
// Prepare
assert.NoError(t, PrepareTestDatabase())
user1, err := GetUserByID(1)
assert.NoError(t, err)
issue1, err := GetIssueByID(1)
assert.NoError(t, err)
issue2, err := GetIssueByID(2)
assert.NoError(t, err)
// Create a dependency and check if it was successful
err = CreateIssueDependency(user1, issue1, issue2)
assert.NoError(t, err)
// Do it again to see if it will check if the dependency already exists
err = CreateIssueDependency(user1, issue1, issue2)
assert.Error(t, err)
assert.True(t, IsErrDependencyExists(err))
// Check for circular dependencies
err = CreateIssueDependency(user1, issue2, issue1)
assert.Error(t, err)
assert.True(t, IsErrCircularDependency(err))
_ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID})
// Check if dependencies left is correct
left, err := IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.False(t, left)
// Close #2 and check again
err = issue2.ChangeStatus(user1, issue2.Repo, true)
assert.NoError(t, err)
left, err = IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.True(t, left)
// Test removing the dependency
err = RemoveIssueDependency(user1, issue1, issue2, DependencyTypeBlockedBy)
assert.NoError(t, err)
}

View File

@ -166,7 +166,7 @@ func (issues IssueList) loadAssignees(e Engine) error {
var assignees = make(map[int64][]*User, len(issues))
rows, err := e.Table("issue_assignees").
Join("INNER", "user", "`user`.id = `issue_assignees`.assignee_id").
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issues.getIssueIDs()).
Rows(new(AssigneeIssue))
if err != nil {

View File

@ -67,7 +67,7 @@ func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error
Where("`issue_watch`.issue_id = ?", issueID).
And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `issue_watch`.user_id").
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id").
Find(&watches)
return
}

View File

@ -192,6 +192,8 @@ var migrations = []Migration{
NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics),
// v69 -> v70
NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable),
// v70 -> v71
NewMigration("add issue_dependencies", addIssueDependencies),
}
// Migrate database to current version

100
models/migrations/v70.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func addIssueDependencies(x *xorm.Engine) (err error) {
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"NOT NULL"`
DependencyID int64 `xorm:"NOT NULL"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"created"`
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"updated"`
}
if err = x.Sync(new(IssueDependency)); err != nil {
return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
}
// Update Comment definition
// This (copied) struct does only contain fields used by xorm as the only use here is to update the database
// CommentType defines the comment type
type CommentType int
// TimeStamp defines a timestamp
type TimeStamp int64
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
IssueID int64 `xorm:"INDEX"`
LabelID int64
OldMilestoneID int64
MilestoneID int64
OldAssigneeID int64
AssigneeID int64
OldTitle string
NewTitle string
DependentIssueID int64
CommitID int64
Line int64
Content string `xorm:"TEXT"`
CreatedUnix TimeStamp `xorm:"INDEX created"`
UpdatedUnix TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"`
}
if err = x.Sync(new(Comment)); err != nil {
return fmt.Errorf("Error updating issue_comment table column definition: %v", err)
}
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type int `xorm:"INDEX(s)"`
Config map[string]interface{} `xorm:"JSON"`
CreatedUnix int64 `xorm:"INDEX CREATED"`
Created time.Time `xorm:"-"`
}
//Updating existing issue units
units := make([]*RepoUnit, 0, 100)
err = x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
if err != nil {
return fmt.Errorf("Query repo units: %v", err)
}
for _, unit := range units {
if unit.Config == nil {
unit.Config = make(map[string]interface{})
}
if _, ok := unit.Config["EnableDependencies"]; !ok {
unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
}
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
return err
}
}
return err
}

View File

@ -118,6 +118,7 @@ func init() {
new(TrackedTime),
new(DeletedBranch),
new(RepoIndexerStatus),
new(IssueDependency),
new(LFSLock),
new(Reaction),
new(IssueAssignees),

View File

@ -383,7 +383,7 @@ func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) {
ous := make([]*OrgUser, 0, 10)
sess := x.
Join("LEFT", "user", "`org_user`.org_id=`user`.id").
Join("LEFT", "`user`", "`org_user`.org_id=`user`.id").
Where("`org_user`.uid=?", uid)
if !all {
// Only show public organizations
@ -575,7 +575,7 @@ func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team,
return teams, e.
Where("`team_user`.org_id = ?", org.ID).
Join("INNER", "team_user", "`team_user`.team_id = team.id").
Join("INNER", "user", "`user`.id=team_user.uid").
Join("INNER", "`user`", "`user`.id=team_user.uid").
And("`team_user`.uid = ?", userID).
Asc("`user`.name").
Cols(cols...).

View File

@ -1345,7 +1345,11 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &IssuesConfig{EnableTimetracker: setting.Service.DefaultEnableTimetracking, AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime},
Config: &IssuesConfig{
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
EnableDependencies: setting.Service.DefaultEnableDependencies,
},
})
} else if tp == UnitTypePullRequests {
units = append(units, RepoUnit{
@ -1954,7 +1958,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) {
var repo Repository
has, err := x.Select("repository.*").
Join("INNER", "user", "`user`.id = repository.owner_id").
Join("INNER", "`user`", "`user`.id = repository.owner_id").
Where("repository.lower_name = ?", strings.ToLower(repoName)).
And("`user`.lower_name = ?", strings.ToLower(ownerName)).
Get(&repo)

View File

@ -73,6 +73,7 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
type IssuesConfig struct {
EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableDependencies bool
}
// FromDB fills up a IssuesConfig from serialized format.
@ -165,7 +166,6 @@ func (r *RepoUnit) IssuesConfig() *IssuesConfig {
func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
return r.Config.(*ExternalTrackerConfig)
}
func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
return units, e.Where("repo_id = ?", repoID).Find(&units)
}

View File

@ -54,7 +54,7 @@ func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
return watches, e.Where("`watch`.repo_id=?", repoID).
And("`user`.is_active=?", true).
And("`user`.prohibit_login=?", false).
Join("INNER", "user", "`user`.id = `watch`.user_id").
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
Find(&watches)
}

View File

@ -374,9 +374,9 @@ func (u *User) GetFollowers(page int) ([]*User, error) {
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("follow.follow_id=?", u.ID)
if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`)
sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
} else {
sess = sess.Join("LEFT", "follow", "user.id=follow.user_id")
sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
}
return users, sess.Find(&users)
}
@ -393,9 +393,9 @@ func (u *User) GetFollowing(page int) ([]*User, error) {
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("follow.user_id=?", u.ID)
if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`)
sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
} else {
sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id")
sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
}
return users, sess.Find(&users)
}

View File

@ -113,6 +113,7 @@ type RepoSettingForm struct {
PullsAllowSquash bool
EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool
// Admin settings
EnableHealthCheck bool

View File

@ -104,6 +104,11 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
r.IsWriter() || issue.IsPoster(user.ID) || isAssigned)
}
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
return r.Repository.IsDependenciesEnabled() && r.IsWriter()
}
// GetCommitsCount returns cached commit count for current view
func (r *Repository) GetCommitsCount() (int64, error) {
var contextName string

View File

@ -85,9 +85,12 @@ type link struct {
var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`)
func isOidValid(oid string) bool {
return oidRegExp.MatchString(oid)
}
// ObjectOidHandler is the main request routing entry point into LFS server functions
func ObjectOidHandler(ctx *context.Context) {
if !setting.LFS.StartServer {
writeStatus(ctx, 404)
return
@ -110,6 +113,11 @@ func ObjectOidHandler(ctx *context.Context) {
}
func getAuthenticatedRepoAndMeta(ctx *context.Context, rv *RequestVars, requireWrite bool) (*models.LFSMetaObject, *models.Repository) {
if !isOidValid(rv.Oid) {
writeStatus(ctx, 404)
return nil, nil
}
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
if err != nil {
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
@ -222,7 +230,7 @@ func PostHandler(ctx *context.Context) {
return
}
if !oidRegExp.MatchString(rv.Oid) {
if !isOidValid(rv.Oid) {
writeStatus(ctx, 404)
return
}
@ -249,7 +257,6 @@ func PostHandler(ctx *context.Context) {
// BatchHandler provides the batch api
func BatchHandler(ctx *context.Context) {
if !setting.LFS.StartServer {
writeStatus(ctx, 404)
return
@ -266,6 +273,10 @@ func BatchHandler(ctx *context.Context) {
// Create a response object
for _, object := range bv.Objects {
if !isOidValid(object.Oid) {
continue
}
repository, err := models.GetRepositoryByOwnerAndName(object.User, object.Repo)
if err != nil {
@ -292,12 +303,10 @@ func BatchHandler(ctx *context.Context) {
continue
}
if oidRegExp.MatchString(object.Oid) {
// Object is not found
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
if err == nil {
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
}
// Object is not found
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
if err == nil {
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
}
}

58
modules/markup/csv/csv.go Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package markup
import (
"bytes"
"encoding/csv"
"html"
"io"
"code.gitea.io/gitea/modules/markup"
)
func init() {
markup.RegisterParser(Parser{})
}
// Parser implements markup.Parser for orgmode
type Parser struct {
}
// Name implements markup.Parser
func (Parser) Name() string {
return "csv"
}
// Extensions implements markup.Parser
func (Parser) Extensions() []string {
return []string{".csv"}
}
// Render implements markup.Parser
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
rd := csv.NewReader(bytes.NewReader(rawBytes))
var tmpBlock bytes.Buffer
tmpBlock.WriteString(`<table class="table">`)
for {
fields, err := rd.Read()
if err == io.EOF {
break
}
if err != nil {
continue
}
tmpBlock.WriteString("<tr>")
for _, field := range fields {
tmpBlock.WriteString("<td>")
tmpBlock.WriteString(html.EscapeString(field))
tmpBlock.WriteString("</td>")
}
tmpBlock.WriteString("<tr>")
}
tmpBlock.WriteString("</table>")
return tmpBlock.Bytes()
}

View File

@ -0,0 +1,25 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package markup
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRenderCSV(t *testing.T) {
var parser Parser
var kases = map[string]string{
"a": "<table class=\"table\"><tr><td>a</td><tr></table>",
"1,2": "<table class=\"table\"><tr><td>1</td><td>2</td><tr></table>",
"<br/>": "<table class=\"table\"><tr><td>&lt;br/&gt;</td><tr></table>",
}
for k, v := range kases {
res := parser.Render([]byte(k), "", nil, false)
assert.EqualValues(t, v, string(res))
}
}

View File

@ -1180,6 +1180,7 @@ var Service struct {
DefaultAllowCreateOrganization bool
EnableTimetracking bool
DefaultEnableTimetracking bool
DefaultEnableDependencies bool
DefaultAllowOnlyContributorsToTrackTime bool
NoReplyAddress string
@ -1210,6 +1211,7 @@ func newService() {
if Service.EnableTimetracking {
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
}
Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")

View File

@ -32,16 +32,8 @@ twofa_scratch=Zwei-Faktor-Einmalpasswort
passcode=PIN
u2f_insert_key=Hardware-Sicherheitsschlüssel einstecken
u2f_sign_in=Drücke den Knopf auf deinem Sicherheitsschlüssel. Wenn deiner keinen Knopf hat, stecke ihn erneut ein.
u2f_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel…
u2f_use_twofa=Zwei-Faktor-Authentifizierung via Handy verwenden
u2f_error=Wir können deinen Hardware-Sicherheitsschlüssel nicht lesen!
u2f_unsupported_browser=Dein Browser unterstützt keine U2F-Geräte. Bitte benutze einen anderen Browser.
u2f_error_1=Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.
u2f_error_2=Stelle sicher, dass du einen verschlüsselte Verbindung (https://) benutzt und die richtige URL eingeben hast.
u2f_error_3=Der Server kann deine Anfrage nicht bearbeiten.
u2f_error_4=Dieser Sicherheitsschlüssel ist nicht berechtigt. Wenn du versuchst, einen neuen Sicherheitsschlüssel zu registrieren, stelle bitte sicher, dass du ihn nicht bereits registriert hast.
u2f_error_5=Das Zeitlimit wurde erreicht bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite neu.
u2f_reload=Neu laden
repository=Repository
@ -130,7 +122,6 @@ federated_avatar_lookup=Föderierte Profilbilder einschalten
federated_avatar_lookup_popup=Föderierte Profilbilder via Libravatar aktivieren.
disable_registration=Registrierung deaktivieren
disable_registration_popup=Registrierung neuer Benutzer deaktivieren. Nur Administratoren werden neue Benutzerkonten anlegen können.
allow_only_external_registration_popup=Registrierung nur über externe Services aktiveren.
openid_signin=OpenID-Anmeldung aktivieren
openid_signin_popup=Benutzeranmeldung via OpenID aktivieren.
openid_signup=OpenID-Selbstregistrierung aktivieren
@ -463,13 +454,10 @@ then_enter_passcode=Und gebe dann die angezeigte PIN der Anwendung ein:
passcode_invalid=Die PIN ist falsch. Probiere es erneut.
twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre dein Einmalpasswort (%s) an einem sicheren Ort auf, da es nicht wieder angezeigt werden wird.
u2f_desc=Hardware-Sicherheitsschlüssel sind Geräte, die kryptografische Schlüssel beinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Der Sicherheitsschlüssel muss den Standard <a href="https://fidoalliance.org/">FIDO U2F</a> unterstützen.
u2f_require_twofa=Du musst die Zwei-Faktor-Authentifizierung aktivieren, um Hardware-Sicherheitsschlüssel nutzen zu können.
u2f_register_key=Sicherheitsschlüssel hinzufügen
u2f_nickname=Nickname
u2f_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel, um diesen zu registrieren.
u2f_delete_key=Sicherheitsschlüssel entfernen
u2f_delete_key_desc=Wenn du den Sicherheitsschlüssel entfernst, kannst du dich nicht mehr mit diesem einloggen. Bist du sicher?
manage_account_links=Verknüpfte Accounts verwalten
manage_account_links_desc=Diese externen Accounts sind mit deinem Gitea-Account verknüpft.
@ -650,7 +638,6 @@ issues.new.open_milestone=Offene Meilensteine
issues.new.closed_milestone=Geschlossene Meilensteine
issues.new.assignees=Zuständig
issues.new.clear_assignees=Zuständige entfernen
issues.new.no_assignees=Niemand zugewiesen
issues.no_ref=Keine Branch/Tag angegeben
issues.create=Issue erstellen
issues.new_label=Neues Label
@ -1167,8 +1154,6 @@ branch.protected_deletion_failed=Branch „%s“ ist geschützt und kann nicht g
topic.manage_topics=Themen verwalten
topic.done=Fertig
topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen
topic.format_prompt=Themen müssen mit einem Buchstaben oder einer Zahl beginnen. Sie können Bindestriche (-) enthalten und dürfen nicht länger als 35 Zeichen sein
[org]
org_name_holder=Name der Organisation
@ -1273,8 +1258,6 @@ dashboard.operation_switch=Wechseln
dashboard.operation_run=Ausführen
dashboard.clean_unbind_oauth=Nicht verbundene OAuth-Verbindungen löschen
dashboard.clean_unbind_oauth_success=Alle unverbundene OAuth-Verbindungen wurden gelöscht.
dashboard.delete_inactivate_accounts=Lösche alle nicht-aktivierten Accounts
dashboard.delete_inactivate_accounts_success=Alle nicht-aktivierten Accounts wurden gelöscht.
dashboard.delete_repo_archives=Alle Repository-Archive löschen
dashboard.delete_repo_archives_success=Alle Repository-Archive wurden gelöscht.
dashboard.delete_missing_repos=Alle Repository-Datensätze mit verlorenen gegangenen Git-Dateien löschen
@ -1482,7 +1465,6 @@ config.db_path=Verzeichnis
config.service_config=Service-Konfiguration
config.register_email_confirm=E-Mail-Bestätigung benötigt zum Registrieren
config.disable_register=Selbstegistrierung deaktivieren
config.allow_only_external_registration=Registrierung nur über externe Services aktiveren
config.enable_openid_signup=OpenID-Selbstregistrierung aktivieren
config.enable_openid_signin=OpenID-Anmeldung aktivieren
config.show_registration_button=Schaltfläche zum Registrieren anzeigen

View File

@ -32,16 +32,16 @@ twofa_scratch = Two-Factor Scratch Code
passcode = Passcode
u2f_insert_key = Insert your security key
u2f_sign_in = Press the button on your security key. If you can't find a button, re-insert it.
u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it.
u2f_press_button = Please press the button on your security key…
u2f_use_twofa = Use a two-factor code from your phone
u2f_error = We can't read your security key!
u2f_unsupported_browser = Your browser don't support U2F keys. Please try another browser.
u2f_error_1 = An unknown error occured. Please retry.
u2f_error_2 = Please make sure that you're using an encrypted connection (https://) and visiting the correct URL.
u2f_error_3 = The server could not proceed your request.
u2f_error_4 = The presented key is not eligible for this request. If you try to register it, make sure that the key isn't already registered.
u2f_error_5 = Timeout reached before your key could be read. Please reload to retry.
u2f_error = Could not read your security key.
u2f_unsupported_browser = Your browser does not support U2F security keys.
u2f_error_1 = An unknown error occurred. Please retry.
u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL.
u2f_error_3 = The server could not process your request.
u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered.
u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry.
u2f_reload = Reload
repository = Repository
@ -130,7 +130,7 @@ federated_avatar_lookup = Enable Federated Avatars
federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar.
disable_registration = Disable Self-Registration
disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts.
allow_only_external_registration_popup=Enable the registration only through external services.
allow_only_external_registration_popup = Allow Registration Only Through External Services
openid_signin = Enable OpenID Sign-In
openid_signin_popup = Enable user sign-in via OpenID.
openid_signup = Enable OpenID Self-Registration
@ -463,13 +463,13 @@ then_enter_passcode = And enter the passcode shown in the application:
passcode_invalid = The passcode is incorrect. Try again.
twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once!
u2f_desc = Security keys are hardware devices containing cryptographic keys. They could be used for two factor authentication. The security key must support the <a href="https://fidoalliance.org/">FIDO U2F</a> standard.
u2f_require_twofa = Two-Factor-Authentication must be enrolled in order to use security keys.
u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" href="https://fidoalliance.org/">FIDO U2F</a> standard.
u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys.
u2f_register_key = Add Security Key
u2f_nickname = Nickname
u2f_press_button = Press the button on your security key to register it.
u2f_delete_key = Remove Security Key
u2f_delete_key_desc= If you remove a security key you cannot login with it anymore. Are you sure?
u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue?
manage_account_links = Manage Linked Accounts
manage_account_links_desc = These external accounts are linked to your Gitea account.
@ -650,7 +650,7 @@ issues.new.open_milestone = Open Milestones
issues.new.closed_milestone = Closed Milestones
issues.new.assignees = Assignees
issues.new.clear_assignees = Clear assignees
issues.new.no_assignees = Nobody assigned
issues.new.no_assignees = No Assignees
issues.no_ref = No Branch/Tag Specified
issues.create = Create Issue
issues.new_label = New Label
@ -781,7 +781,33 @@ issues.due_date_added = "added the due date %s %s"
issues.due_date_modified = "modified the due date to %s from %s %s"
issues.due_date_remove = "removed the due date %s %s"
issues.due_date_overdue = "Overdue"
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format yyyy-mm-dd."
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'."
issues.dependency.title = Dependencies
issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies.
issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies.
issues.dependency.add = Add dependency...
issues.dependency.cancel = Cancel
issues.dependency.remove = Remove
issues.dependency.added_dependency = `<a href="%[1]s">%[2]s</a> added a new dependency %[3]s`
issues.dependency.removed_dependency = `<a href="%[1]s">%[2]s</a> removed a dependency %[3]s`
issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues
issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues
issues.dependency.issue_close_blocks = This issue blocks closing of the following issues
issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues
issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it.
issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it.
issues.dependency.blocks_short = Blocks
issues.dependency.blocked_by_short = Depends on
issues.dependency.remove_header = Remove Dependency
issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue?
issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue?
issues.dependency.setting = Enable Dependencies For Issues and Pull Requests
issues.dependency.add_error_same_issue = You cannot make an issue depend on itself.
issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist.
issues.dependency.add_error_dep_not_exist = Dependency does not exist.
issues.dependency.add_error_dep_exists = Dependency already exists.
issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other.
issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository.
pulls.desc = Enable merge requests and code reviews.
pulls.new = New Pull Request
@ -1170,8 +1196,8 @@ branch.protected_deletion_failed = Branch '%s' is protected. It cannot be delete
topic.manage_topics = Manage Topics
topic.done = Done
topic.count_prompt = You can't select more than 25 topics
topic.format_prompt = Topics must start with a letter or number, can include hyphens(-) and must be no more than 35 characters long
topic.count_prompt = You can not select more than 25 topics
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
[org]
org_name_holder = Organization Name
@ -1276,8 +1302,8 @@ dashboard.operation_switch = Switch
dashboard.operation_run = Run
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
dashboard.delete_inactivate_accounts = Delete all not activated accounts
dashboard.delete_inactivate_accounts_success = All not activated accounts have been deleted.
dashboard.delete_inactivate_accounts = Delete all unactivated accounts
dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted.
dashboard.delete_repo_archives = Delete all repository archives
dashboard.delete_repo_archives_success = All repository archives have been deleted.
dashboard.delete_missing_repos = Delete all repositories missing their Git files
@ -1485,7 +1511,7 @@ config.db_path = Path
config.service_config = Service Configuration
config.register_email_confirm = Require Email Confirmation to Register
config.disable_register = Disable Self-Registration
config.allow_only_external_registration = Enable the registration only through external services
config.allow_only_external_registration = Allow Registration Only Through External Services
config.enable_openid_signup = Enable OpenID Self-Registration
config.enable_openid_signin = Enable OpenID Sign-In
config.show_registration_button = Show Register Button
@ -1501,6 +1527,7 @@ config.enable_timetracking = Enable Time Tracking
config.default_enable_timetracking = Enable Time Tracking by Default
config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
config.no_reply_address = Hidden Email Domain
config.default_enable_dependencies = Enable Issue Dependencies by Default
config.webhook_config = Webhook Configuration
config.queue_length = Queue Length

View File

@ -32,11 +32,6 @@ passcode=Contraseña
u2f_insert_key=Inserte su clave de seguridad
u2f_use_twofa=Use un código de dos factores de su celular
u2f_error=No podemos leer su llave de seguridad!
u2f_unsupported_browser=Su navegador no soporta llaves U2F. Por favor utilicé otro navegador.
u2f_error_1=Un error desconocido ha ocurrido. Por favor vuelva a intentarlo.
u2f_error_2=Por favor asegúrese de que está utilizando una conexión cifrada (https://) y que esta visitando la dirección URL correcta.
u2f_error_3=El servidor no pudo procesar su petición.
u2f_reload=Recargar
repository=Repositorio

View File

@ -32,16 +32,8 @@ twofa_scratch=Code de secours pour l'authentification à deux facteurs
passcode=Code d'accès
u2f_insert_key=Insérez votre clef de sécurité
u2f_sign_in=Appuyez sur le bouton de votre clef de sécurité. Si vous ne voyez pas de bouton, ré-insérez là.
u2f_press_button=Veuillez appuyer sur le bouton de votre clef de sécurité…
u2f_use_twofa=Utilisez l'authentification à deux facteurs avec votre téléphone
u2f_error=Nous ne pouvons pas lire votre clé de sécurité !
u2f_unsupported_browser=Votre navigateur n'est pas compatible avec les clefs U2F. Veuillez réessayer avec un autre navigateur.
u2f_error_1=Une erreur inconnue s'est produite. Veuillez réessayer.
u2f_error_2=Veuillez vous assurer que vous utilisez une connexion chiffrée (https) et que vous visitez la bonne adresse.
u2f_error_3=Le serveur n'a pas pu répondre à votre requête.
u2f_error_4=Cette clef n'est pas compatible avec votre requête. Si vous êtes en train de l'enregistrer, veuillez vous assurer que cette clef n'est pas déjà enregistrée.
u2f_error_5=Le délai d'attente imparti a été atteint avant que votre clef ne puisse être lue. Veuillez recharger la page pour réessayer.
u2f_reload=Recharger
repository=Dépôt
@ -129,7 +121,6 @@ federated_avatar_lookup=Activer les avatars unifiés
federated_avatar_lookup_popup=Activer la recherche unifiée d'avatars en utilisant le service open source unifié basé sur libravatar.
disable_registration=Désactiver le formulaire d'inscription
disable_registration_popup=Désactiver les nouvelles inscriptions. Seuls les administrateurs pourront créer de nouveaux comptes utilisateurs.
allow_only_external_registration_popup=N'autoriser l'inscription qu'à partir des services externes.
openid_signin=Activer l'inscription OpenID
openid_signin_popup=Activer l'authentification via OpenID.
openid_signup=Activer l'inscription OpenID
@ -462,13 +453,10 @@ then_enter_passcode=Et entrez le mot de passe s'affichant dans l'application :
passcode_invalid=Le mot de passe est invalide. Réessayez.
twofa_enrolled=L'authentification à deux facteurs a été activée pour votre compte. Gardez votre jeton de secours (%s) en lieu sûr car il ne vous sera montré qu'une seule fois !
u2f_desc=Les clefs de sécurité sont des dispositifs matériels contenant des clefs cryptographiques. Elles peuvent être utilisées pour l'authentification à deux facteurs. La clef de sécurité doit supporter le standard <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=L'authentification à deux facteurs doit être activée afin d'utiliser une clef de sécurité.
u2f_register_key=Ajouter une clef de sécurité
u2f_nickname=Pseudonyme
u2f_press_button=Appuyer sur le bouton de votre clef de sécurité pour l'enregistrer.
u2f_delete_key=Supprimer une clef de sécurité
u2f_delete_key_desc=Si vous supprimez une clef de sécurité vous ne pourrez plus l'utiliser pour vous connecter. Continuer?
manage_account_links=Gérer les comptes liés
manage_account_links_desc=Ces comptes externes sont liés à votre compte Gitea.
@ -643,7 +631,6 @@ issues.new.open_milestone=Ouvrir un jalon
issues.new.closed_milestone=Jalons fermés
issues.new.assignees=Affecté à
issues.new.clear_assignees=Supprimer les affectations
issues.new.no_assignees=Aucune affectation
issues.no_ref=Aucune branche/tag spécifiés
issues.create=Créer un ticket
issues.new_label=Nouvelle étiquette
@ -1158,8 +1145,6 @@ branch.protected_deletion_failed=La branche '%s' est protégé. Il ne peut pas
topic.manage_topics=Gérer les sujets
topic.done=Terminé
topic.count_prompt=Vous ne pouvez pas sélectionner plus de 25 sujets
topic.format_prompt=Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets (-), sans dépasser 35 caractères
[org]
org_name_holder=Nom de l'organisation
@ -1471,7 +1456,6 @@ config.db_path=Emplacement
config.service_config=Configuration du service
config.register_email_confirm=Exiger la confirmation de l'e-mail lors de l'inscription
config.disable_register=Désactiver le formulaire d'inscription
config.allow_only_external_registration=N'autoriser l'inscription qu'à partir des services externes
config.enable_openid_signup=Activer l'inscription avec OpenID
config.enable_openid_signin=Activer la connexion avec OpenID
config.show_registration_button=Afficher le bouton d'enregistrement

View File

@ -32,16 +32,8 @@ twofa_scratch=Kode Awal Dua Faktor
passcode=Kode Akses
u2f_insert_key=Masukkan kunci keamanan anda
u2f_sign_in=Tekan tombol pada kunci keamanan anda. Jika anda tidak menemukan tombol, masukkan kembali.
u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda…
u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda
u2f_error=Kami tidak bisa membaca kunci keamanan anda!
u2f_unsupported_browser=Browser anda tidak mendukung kunci U2F. Silakan mencoba browser lain.
u2f_error_1=Terdapat kesalahan yang tidak diketahui. Mohon coba lagi.
u2f_error_2=Pastikan bahwa anda menggunakan koneksi terenkripsi (https://) dan mengunjungi URL yang benar.
u2f_error_3=Server tidak bisa melanjutkan permintaan anda.
u2f_error_4=Kunci tidak layak untuk permintaan ini. Jika Anda mencoba untuk mendaftarkanya, pastikan bahwa kunci sudah tidak terdaftar.
u2f_error_5=Timeout tercapai sebelum kunci anda bisa terbaca. Silahkan muat ulang untuk mencoba kembali.
u2f_reload=Muat Ulang
repository=Repositori

View File

@ -32,16 +32,8 @@ twofa_scratch=Codice di recupero per la verifica in due passaggi
passcode=Codice di sicurezza
u2f_insert_key=Inserisci la chiave di sicurezza
u2f_sign_in=Premi il pulsante sulla tua chiave di sicurezza. Se non riesci a trovare alcun pulsante, reinseriscilo.
u2f_press_button=Si prega di premere il pulsante sulla tua chiave di sicurezza…
u2f_use_twofa=Usa un codice di verifica in due passaggi dal tuo telefono
u2f_error=Non riusciamo a leggere la tua chiave di sicurezza!
u2f_unsupported_browser=Il tuo browser non supporta le chiavi U2F. Si prega di provare un altro browser.
u2f_error_1=Si è verificato un errore sconosciuto. Si prega di riprovare.
u2f_error_2=Si prega di assicurarsi che si sta utilizza una connessione crittografata (https://) e visitando l'URL corretto.
u2f_error_3=Il server non ha potuto eseguire la richiesta.
u2f_error_4=La chiave data non è idonea per questa richiesta. Se stai provando a registrarla, assicurati che la chiave non sia già stata registrata.
u2f_error_5=Timeout raggiunto prima che la tua chiave potesse essere letta. Si prega di ricaricare per riprovare.
u2f_reload=Ricarica
repository=Repository
@ -129,7 +121,6 @@ federated_avatar_lookup=Attiva i Federated Avatar
federated_avatar_lookup_popup=Enable federated avatars lookup to use federated open source service based on libravatar.
disable_registration=Disattiva Self-Registration
disable_registration_popup=Disattiva la user self-registration. Solo gli amministratori saranno in grado di creare account.
allow_only_external_registration_popup=Attiva la registrazione solo tramite servizi esterni.
openid_signin=Attiva l'accesso OpenID
openid_signin_popup=Attiva registrazione utente via OpenID.
openid_signup=Attiva OpenID Self-Registration
@ -462,14 +453,10 @@ then_enter_passcode=E immetti il codice di accesso indicato nell'applicazione:
passcode_invalid=Il codice di accesso non è corretto. Riprova.
twofa_enrolled=Il tuo account è stato registrato alla verifica in due passaggi. Conserva il token di sicurezza (%s) in un luogo sicuro in quanto viene visualizzato sono una volta!
u2f_desc=Le chiavi di sicurezza sono dispositivi hardware contenenti chiavi crittografiche. Potrebbero essere usate per la verifica in due passaggi.
La chiave di sicurezza deve supportare lo standard <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=La verifica in due passaggi deve essere attiva per poter utilizzare le chiavi di protezione.
u2f_register_key=Aggiungi chiave di sicurezza
u2f_nickname=Nickname
u2f_press_button=Premi il pulsante sulla tua chiave di sicurezza per registrarla.
u2f_delete_key=Rimuovi chiave di sicurezza
u2f_delete_key_desc=Se si rimuove una chiave di sicurezza non sarà più possibile effettuare l'accesso con essa. Sei sicuro?
manage_account_links=Gestisci gli account collegati
manage_account_links_desc=Questi account esterni sono collegati al tuo account Gitea.
@ -644,7 +631,6 @@ issues.new.open_milestone=Apri Milestone
issues.new.closed_milestone=Milestone chiuse
issues.new.assignees=Assegnatari
issues.new.clear_assignees=Cancella assegnatari
issues.new.no_assignees=Nessuno assegnato
issues.no_ref=Nessun Branch/Tag specificato
issues.create=Crea Problema
issues.new_label=Nuova etichetta
@ -1159,8 +1145,6 @@ branch.protected_deletion_failed=Il branch '%s' è protetto. Non può essere eli
topic.manage_topics=Gestisci argomenti
topic.done=Fatto
topic.count_prompt=Non puoi selezione più di 25 argomenti
topic.format_prompt=Gli argomenti devono iniziare con una lettera o un numero, possono includere il carattere hiphens(-) e non possono essere più lunghi di 35 caratteri
[org]
org_name_holder=Nome dell'Organizzazione
@ -1472,7 +1456,6 @@ config.db_path=Percorso
config.service_config=Configurazione Servizio
config.register_email_confirm=Richiedere la conferma Email per registrarsi
config.disable_register=Disattiva Self-Registration
config.allow_only_external_registration=Attiva la registrazione solo tramite servizi esterni
config.enable_openid_signup=Attiva OpenID Self-Registration
config.enable_openid_signin=Attiva l'accesso tramite OpenID
config.show_registration_button=Mostra Pulsane Registrazione

View File

@ -32,16 +32,8 @@ twofa_scratch=Divu faktoru vienreizējais kods
passcode=Kods
u2f_insert_key=Ievietojiet Jūsu drošības atslēgu
u2f_sign_in=Nospiediet drošības atslēgas pogu. Ja nevarat to atrast, ievietojiet atkārtoti.
u2f_press_button=Nospiediet drošības atslēgas pogu…
u2f_use_twofa=Izmantot divu faktoru kodu no tālruņa
u2f_error=Nav iespējams nolasīt drošības atslēgu!
u2f_unsupported_browser=Jūsu pārlūks neatbalsta U2F atslēgas. Izmantojiet citu pārlūku.
u2f_error_1=Notikusi nezināma kļūda. Atkārtojiet darbību vēlreiz.
u2f_error_2=Pārliecinieties, ka izmantojat šifrētu savienojumu (https://) un apmeklējat pareizo URL.
u2f_error_3=Serveris nevar apstrādāt Jūsu pieprasījumu.
u2f_error_4=Nav iespējams izmantot atslēgu šim pieprasījumam. Ja mēģināt to reģistrēt, pārliecinieties, ka atslēga jau nav reģistrēta.
u2f_error_5=Iestājusies noildze nolasot atslēgu. Pārlādējiet lapu, lai atkārtotu vēlreiz.
u2f_reload=Pārlādēt
repository=Repozitorijs
@ -130,7 +122,6 @@ federated_avatar_lookup=Iespējot apvienotās profila bildes
federated_avatar_lookup_popup=Iespējot apvienoto profila bilžu meklētāju, lai izmantotu atvērtā koda apvienoto servisu balstītu uz libravatar.
disable_registration=Atspējot lietotāju reģistrāciju
disable_registration_popup=Atspējot iespēju reģistrēties. Tikai administratori varēs izveidot jaunus kontus.
allow_only_external_registration_popup=Ļaut reģistrēties tikai izmantojot ārējos pakalpojumus.
openid_signin=Iespējot OpenID autorizāciju
openid_signin_popup=Iespējot lietotāju autorizāciju ar OpenID.
openid_signup=Iespējot reģistrāciju, izmantojot OpenID
@ -463,13 +454,10 @@ then_enter_passcode=Ievadiet piekļuves kodu no lietojumprogrammas:
passcode_invalid=Nederīgs piekļuves kods. Mēģiniet ievadīt atkārtoti.
twofa_enrolled=Kontam tagad ir ieslēgta divu faktoru autentifikācija. Saglabājiet savu vienreizējo kodu (%s), jo tas vairāk netiks parādīts!
u2f_desc=Drošības atslēgas ir aparatūras ierīces, kas satur kriptogrāfiskās atslēgas. Tās var tikt izmantotas, lai nodrošinātu divu faktoru autentifikāciju. Drošības atslēgām ir jāatbalsta <a href="https://fidoalliance.org/">FIDO U2F</a> standarts.
u2f_require_twofa=Jābūt iespējotai divu faktoru autentifikācija, lai varētu izmantot drošības atslēgas.
u2f_register_key=Pievienot drošības atslēgu
u2f_nickname=Segvārds
u2f_press_button=Nospiediet pogu uz Jūsu drošības atslēgas, lai to reģistrētu.
u2f_delete_key=Noņemt drošības atslēgu
u2f_delete_key_desc=Noņemot drošības atslēgu ar to vairs nebūs iespējams autorizēties. Vai turpināt?
manage_account_links=Pārvaldīt saistītos kontus
manage_account_links_desc=Šādi ārējie konti ir piesaistīti Jūsu Gitea kontam.
@ -646,7 +634,6 @@ issues.new.open_milestone=Atvērtie atskaites punktus
issues.new.closed_milestone=Aizvērtie atskaites punkti
issues.new.assignees=Atbildīgie
issues.new.clear_assignees=Noņemt atbildīgo
issues.new.no_assignees=Nav atbildīgā
issues.no_ref=Nav norādīts atzars/tags
issues.create=Pieteikt problēmu
issues.new_label=Jauna etiķete
@ -1163,8 +1150,6 @@ branch.protected_deletion_failed=Atzars '%s' ir aizsargāts. To nevar izdzēst.
topic.manage_topics=Pārvaldīt tēmas
topic.done=Gatavs
topic.count_prompt=Nevar pievienot vairāk kā 25 tēmas
topic.format_prompt=Tēmas nosaukumam ir jāsākas ar burtu vai ciparu, tas var saturēt defisi(-), kā arī tas nevar būt garāks par 35 simboliem
[org]
org_name_holder=Organizācijas nosaukums
@ -1269,8 +1254,6 @@ dashboard.operation_switch=Pārslēgt
dashboard.operation_run=Palaist
dashboard.clean_unbind_oauth=Notīrīt nepiesaistītos OAuth savienojumus
dashboard.clean_unbind_oauth_success=Visi nepiesaistītie OAuth savienojumu tika izdzēsti.
dashboard.delete_inactivate_accounts=Dzēst visus neaktivizētos kontus
dashboard.delete_inactivate_accounts_success=Visi neaktivizētie konti tika izdzēsti.
dashboard.delete_repo_archives=Dzēst visu repozitoriju arhīvus
dashboard.delete_repo_archives_success=Visu repozitoriju arhīvi tika izdzēsti.
dashboard.delete_missing_repos=Dzēst visus repozitorijus, kam trūkst Git failu
@ -1478,7 +1461,6 @@ config.db_path=Ceļš
config.service_config=Pakalpojuma konfigurācija
config.register_email_confirm=Reģistrējoties pieprasīt apstiprināt e-pasta adresi
config.disable_register=Atspējot lietotāju reģistrāciju
config.allow_only_external_registration=Ļaut reģistrēties tikai izmantojot ārējos pakalpojumus
config.enable_openid_signup=Iespējot reģistrāciju, izmantojot OpenID
config.enable_openid_signin=Iespējot OpenID autorizāciju
config.show_registration_button=Rādīt reģistrēšanās pogu

View File

@ -32,14 +32,8 @@ twofa_scratch=Eenmalige twee factor authenticatie code
passcode=PIN
u2f_insert_key=Uw beveiligingssleutel invoegen
u2f_sign_in=Druk op de knop op uw beveiligingssleutel. Als u een knop niet kunt vinden, deze opnieuw invoegen.
u2f_press_button=Druk op de knop op uw beveiligingssleutel…
u2f_use_twofa=Gebruik een twee-factor code van uw telefoon
u2f_error=Wij kunnen niet uw beveiligingssleutel lezen!
u2f_unsupported_browser=Uw browser geen ondersteund U2F keys. Probeer een andere browser.
u2f_error_1=Er is een onbekende fout opgetreden. Probeer het opnieuw.
u2f_error_2=Zorg voor een versleutelde verbinding (https://) en een bezoek aan de juiste URL.
u2f_error_3=De server kan uw aanvraag niet verhandelen.
u2f_reload=Herladen
repository=Repository

View File

@ -32,16 +32,16 @@ twofa_scratch=Código de backup da autenticação de dois fatores
passcode=Senha
u2f_insert_key=Insira sua chave de segurança
u2f_sign_in=Pressione o botão na sua chave de segurança. Se você não encontrar um botão, insira-o novamente.
u2f_sign_in=Pressione o botão na sua chave de segurança. Se a sua chave de segurança não tiver um botão, insira-a novamente.
u2f_press_button=Por favor, pressione o botão na sua chave de segurança...
u2f_use_twofa=Use um código de dois fatores no seu telefone
u2f_error=Não conseguimos ler sua chave de segurança!
u2f_unsupported_browser=Seu navegador não suporta chaves U2F. Por favor, tente outro navegador.
u2f_error=Não foi possível ler sua chave de segurança.
u2f_unsupported_browser=Seu navegador não suporta chaves de segurança U2F.
u2f_error_1=Ocorreu um erro desconhecido. Por favor, tente novamente.
u2f_error_2=Por favor, certifique-se de que você está usando uma conexão criptografada (https://) e visite a URL correta.
u2f_error_3=O servidor não pôde prosseguir com sua solicitação.
u2f_error_4=A chave apresentada não é elegível para esta solicitação. Se você tentar registrá-la, certifique-se de que a chave já não é registrada.
u2f_error_5=Tempo limite atingido antes de sua chave poder ser lida. Por favor, recarregue para tentar novamente.
u2f_error_2=Por favor, certifique-se de usar a URL correto, criptografado (https://).
u2f_error_3=O servidor não pôde processar sua solicitação.
u2f_error_4=A chave de segurança não é permitida para esta solicitação. Por favor, certifique-se que a chave já não está registrada.
u2f_error_5=Tempo limite atingido antes de sua chave poder ser lida. Por favor, recarregue esta página e tente novamente.
u2f_reload=Recarregar
repository=Repositório
@ -53,7 +53,7 @@ new_mirror=Novo mirror
new_fork=Novo Fork de Repositório
new_org=Nova organização
manage_org=Gerenciar organizações
admin_panel=Administração do site
admin_panel=Administração geral
account_settings=Configurações da conta
settings=Configurações
your_profile=Perfil
@ -93,7 +93,7 @@ no_admin_and_disable_registration=Você não pode desabilitar o auto-cadastro do
err_empty_admin_password=A senha do administrador não pode ser em branco.
general_title=Configurações gerais
app_name=Título do site
app_name=Nome do servidor
app_name_helper=Você pode inserir o nome da empresa aqui.
repo_path=Caminho raíz do repositório
repo_path_helper=Todos os repositórios remotos do Git serão salvos neste diretório.
@ -130,7 +130,7 @@ federated_avatar_lookup=Habilitar avatares federativos
federated_avatar_lookup_popup=Habilitar a busca federativa de avatares a usar o serviço federativo de código aberto baseado no libravatar.
disable_registration=Desabilitar auto-cadastro
disable_registration_popup=Desabilitar auto-cadastro de usuário. Somente os administradores serão capazes de criar novas contas de usuário.
allow_only_external_registration_popup=Habilitar o cadastro apenas por meio de serviços externos.
allow_only_external_registration_popup=Permitir cadastro somente por meio de serviços externos
openid_signin=Habilitar acesso via OpenID
openid_signin_popup=Habilitar o acesso de usuários via OpenID.
openid_signup=Habilitar o auto-cadastro via OpenID
@ -463,13 +463,13 @@ then_enter_passcode=E insira a senha mostrada no aplicativo:
passcode_invalid=Esse código de acesso é inválido. Tente novamente.
twofa_enrolled=Sua conta foi inscrita na autenticação de dois fatores. Armazene seu token de backup (%s) em um local seguro, pois ele é exibido apenas uma vez!
u2f_desc=Chaves de segurança são dispositivos de hardware que contém chaves de criptografia. Elas podem ser usadas para autenticação de dois fatores. A chave de segurança deve suportar o padrão <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=Autenticação de dois fatores deve estar inscrita para usar chaves de segurança.
u2f_desc=Chaves de segurança são dispositivos de hardware contendo chaves criptográficas. Eles podem ser usados para autenticação de dois fatores. As chaves de segurança devem suportar o padrão <a rel="noreferrer" href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=Sua conta deve estar inscrita na autenticação de dois fatores para usar as chaves de segurança.
u2f_register_key=Adicionar chave de segurança
u2f_nickname=Apelido
u2f_press_button=Pressione o botão na sua chave de segurança para registrá-la.
u2f_delete_key=Remover chave de segurança
u2f_delete_key_desc=Se você remover uma chave de segurança você não poderá mais acessar com ela. Tem certeza?
u2f_delete_key_desc=Se você remover uma chave de segurança, não poderá mais entrar com ela. Continuar?
manage_account_links=Gerenciar contas vinculadas
manage_account_links_desc=Estas contas externas estão vinculadas a sua conta de Gitea.
@ -597,7 +597,7 @@ editor.name_your_file=Nomeie o seu arquivo…
editor.filename_help=Adicione um diretório digitando seu nome seguido por uma barra ('/'). Remova um diretório digitando o backspace no início do campo de entrada.
editor.or=ou
editor.cancel_lower=Cancelar
editor.commit_changes=Confirmar alterações
editor.commit_changes=Realizar commit das alterações
editor.add_tmpl=Adicionar '%s/<filename>'
editor.add=Adicionar '%s'
editor.update=Atualizar '%s'
@ -650,7 +650,7 @@ issues.new.open_milestone=Marcos abertos
issues.new.closed_milestone=Marcos fechados
issues.new.assignees=Responsáveis
issues.new.clear_assignees=Limpar responsáveis
issues.new.no_assignees=Nenhum responsável
issues.new.no_assignees=Sem responsável
issues.no_ref=Nenhum branch/tag especificado
issues.create=Criar issue
issues.new_label=Nova etiqueta
@ -682,7 +682,7 @@ issues.filter_milestone_no_select=Todos os marcos
issues.filter_assignee=Atribuído
issues.filter_assginee_no_select=Todos os responsáveis
issues.filter_type=Tipo
issues.filter_type.all_issues=Todos os issues
issues.filter_type.all_issues=Todas as issues
issues.filter_type.assigned_to_you=Atribuídos a você
issues.filter_type.created_by_you=Criado por você
issues.filter_type.mentioning_you=Mencionando você
@ -781,6 +781,33 @@ issues.due_date_added=adicionou a data limite %s %s
issues.due_date_modified=modificou a data limite para %s ao invés de %s %s
issues.due_date_remove=removeu a data limite %s %s
issues.due_date_overdue=Em atraso
issues.due_date_invalid=A data limite é inválida ou está fora do intervalo. Por favor, use o formato 'dd/mm/aaaa'.
issues.dependency.title=Dependências
issues.dependency.issue_no_dependencies=Esta issue atualmente não tem dependências.
issues.dependency.pr_no_dependencies=Este pull request atualmente não tem dependências.
issues.dependency.add=Adicione...
issues.dependency.cancel=Cancelar
issues.dependency.remove=Remover
issues.dependency.added_dependency=`<a href="%[1]s">%[2]s</a> adicionou uma nova dependência %[3]s`
issues.dependency.removed_dependency=`<a href="%[1]s">%[2]s</a> removeu uma dependência %[3]s`
issues.dependency.issue_closing_blockedby=Fechamento deste pull request está bloqueado pelas seguintes issues
issues.dependency.pr_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues
issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues
issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues
issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la.
issues.dependency.pr_close_blocked=Você precisa fechar todas issues que bloqueiam este pull request antes de poder fazer o merge.
issues.dependency.blocks_short=Bloqueia
issues.dependency.blocked_by_short=Depende de
issues.dependency.remove_header=Remover dependência
issues.dependency.issue_remove_text=Isto removerá a dependência desta issue. Continuar?
issues.dependency.pr_remove_text=Isto removerá a dependência deste pull request. Continuar?
issues.dependency.setting=Habilitar dependências para issues e pull requests
issues.dependency.add_error_same_issue=Você não pode fazer uma issue depender de si mesma.
issues.dependency.add_error_dep_issue_not_exist=Issue dependente não existe.
issues.dependency.add_error_dep_not_exist=Dependência não existe.
issues.dependency.add_error_dep_exists=Dependência já existe.
issues.dependency.add_error_cannot_create_circular=Você não pode criar uma dependência com duas issues bloqueando uma a outra.
issues.dependency.add_error_dep_not_same_repo=Ambas as issues devem estar no mesmo repositório.
pulls.desc=Habilitar solicitações de merge e revisões de código.
pulls.new=Novo pull request
@ -793,7 +820,7 @@ pulls.no_results=Nada encontrado.
pulls.nothing_to_compare=Estes branches são iguais. Não há nenhuma necessidade para criar um pull request.
pulls.has_pull_request=`Um pull request entre esses branches já existe: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
pulls.create=Criar pull request
pulls.title_desc=quer mesclar %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code>
pulls.title_desc=quer realizar o merge de %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code>
pulls.merged_title_desc=fez merge dos %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code> %[4]s
pulls.tab_conversation=Conversação
pulls.tab_commits=Commits
@ -818,14 +845,14 @@ milestones.new=Novo marco
milestones.open_tab=%d Aberto
milestones.close_tab=%d Fechado
milestones.closed=Fechado %s
milestones.no_due_date=Sem prazo
milestones.no_due_date=Sem data limite
milestones.open=Reabrir
milestones.close=Fechar
milestones.new_subheader=Marcos organizam as issues e acompanham o progresso.
milestones.create=Criar marco
milestones.title=Título
milestones.desc=Descrição
milestones.due_date=Prazo (opcional)
milestones.due_date=Data limite (opcional)
milestones.clear=Limpar
milestones.invalid_due_date_format=Formato da data limite deve ser 'dd/mm/aaaa'.
milestones.create_success=O marco '%s' foi criado.
@ -837,8 +864,8 @@ milestones.edit_success=O marco '%s' foi atualizado.
milestones.deletion=Excluir marco
milestones.deletion_desc=A exclusão deste marco irá removê-lo de todas as issues. Tem certeza que deseja continuar?
milestones.deletion_success=O marco foi excluído.
milestones.filter_sort.closest_due_date=Prazo mais próximo
milestones.filter_sort.furthest_due_date=Prazo mais longe
milestones.filter_sort.closest_due_date=Data limite mais próxima
milestones.filter_sort.furthest_due_date=Data limite mais distante
milestones.filter_sort.least_complete=Menos completo
milestones.filter_sort.most_complete=Mais completo
milestones.filter_sort.most_issues=Com mais issues
@ -894,15 +921,15 @@ activity.closed_issues_count_1=Issue fechada
activity.closed_issues_count_n=Issues fechadas
activity.title.issues_1=+%d Issue
activity.title.issues_n=+%d Issues
activity.title.issues_closed_by=%s fechado por %s
activity.title.issues_created_by=%s criado por %s
activity.title.issues_closed_by=%s fechada por %s
activity.title.issues_created_by=%s criada por %s
activity.closed_issue_label=Fechado
activity.new_issues_count_1=Nova issue
activity.new_issues_count_n=Novas issues
activity.new_issue_label=Aberto
activity.title.unresolved_conv_1=%d conversa não resolvida
activity.title.unresolved_conv_n=%d conversas não resolvidas
activity.unresolved_conv_desc=Estes problemas foram recentemente alterados e pull requests ainda não foram resolvidos.
activity.unresolved_conv_desc=Estas issues foram recentemente alteradas e pull requests ainda não foram resolvidos.
activity.unresolved_conv_label=Abrir
activity.title.releases_1=%d Versão
activity.title.releases_n=%d Versões
@ -975,7 +1002,7 @@ settings.confirm_wiki_delete=Excluir dados da wiki
settings.wiki_deletion_success=Os dados da wiki do repositório foi excluídos.
settings.delete=Excluir este repositório
settings.delete_desc=A exclusão de um repositório é permanente e não pode ser desfeita.
settings.delete_notices_1=-Esta operação <strong>NÃO PODERÁ</strong> ser desfeita.
settings.delete_notices_1=- Esta operação <strong>NÃO PODERÁ</strong> ser desfeita.
settings.delete_notices_2=- Essa operação excluirá permanentemente o repositório <strong>%s</strong>, incluindo código, issues, comentários, dados da wiki e configurações do colaborador.
settings.delete_notices_fork_1=- Forks deste repositório se tornarão independentes após a exclusão.
settings.deletion_success=O repositório foi excluído.
@ -1169,7 +1196,7 @@ branch.protected_deletion_failed=A branch '%s' está protegida. Ela não pode se
topic.manage_topics=Gerenciar Tópicos
topic.done=Feito
topic.count_prompt=Você não pode selecionar mais de 25 tópicos
topic.format_prompt=Tópicos devem começar com uma letra ou um número, podem incluir hífens (-) e não devem ter mais de 35 caracteres
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
[org]
org_name_holder=Nome da organização
@ -1443,7 +1470,7 @@ auths.deletion_success=A fonte de autenticação foi excluída.
auths.login_source_exist=A fonte de autenticação '%s' já existe.
config.server_config=Configuração do servidor
config.app_name=Título do site
config.app_name=Nome do servidor
config.app_ver=Versão do Gitea
config.app_url=URL base do Gitea
config.custom_conf=Caminho do Arquivo de Configuração
@ -1483,7 +1510,7 @@ config.db_path=Caminho
config.service_config=Configuração do serviço
config.register_email_confirm=Exigir confirmação de e-mail para se cadastrar
config.disable_register=Desabilitar auto-cadastro
config.allow_only_external_registration=Habilitar o cadastro apenas por meio de serviços externos
config.allow_only_external_registration=Permitir cadastro somente por meio de serviços externos
config.enable_openid_signup=Habilitar o auto-cadastro via OpenID
config.enable_openid_signin=Habilitar acesso via OpenID
config.show_registration_button=Mostrar botão de cadastro
@ -1499,6 +1526,7 @@ config.enable_timetracking=Habilitar contador de tempo
config.default_enable_timetracking=Habilitar o contador de tempo por padrão
config.default_allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo
config.no_reply_address=Ocultar domínio de e-mail
config.default_enable_dependencies=Habilitar dependências de issue por padrão
config.webhook_config=Configuração de Hook da Web
config.queue_length=Tamanho da fila
@ -1586,7 +1614,7 @@ create_repo=criou o repositório <a href="%s">%s</a>
rename_repo=renomeou o repositório <code>%[1]s</code> para <a href="%[2]s">%[3]s</a>
commit_repo=fez push para <a href="%[1]s/src/%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a>
create_issue=`abriu a issue <a href="%s/issues/%s">%s#%[2]s</a>`
close_issue=`fechou issue <a href="%s/issues/%s">%s#%[2]s</a>`
close_issue=`fechou a issue <a href="%s/issues/%s">%s#%[2]s</a>`
reopen_issue=`reabriu a issue <a href="%s/issues/%s">%s#%[2]s</a>`
create_pull_request=`criou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
close_pull_request=`fechou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>`

View File

@ -32,16 +32,8 @@ twofa_scratch=Двухфакторный scratch-код
passcode=Пароль
u2f_insert_key=Вставьте ключ безопасности
u2f_sign_in=Нажмите кнопку на ключе безопасности. Если вы не можете найти кнопку, вставьте его снова.
u2f_press_button=Пожалуйста нажмите кнопку на вашем ключе безопасности…
u2f_use_twofa=Используйте двухфакторный код с телефона
u2f_error=Мы не можем прочитать ваш ключ безопасности!
u2f_unsupported_browser=Ваш браузер не поддерживает U2F ключи. Попробуйте другой браузер.
u2f_error_1=Произошла неизвестная ошибка. Повторите попытку.
u2f_error_2=Пожалуйста, убедитесь, что вы используете зашифрованное соединение (https://) и используете правильный URL.
u2f_error_3=Серверу не удалось обработать ваш запрос.
u2f_error_4=Представленный ключ не подходит для этого запроса. Если вы пытаетесь зарегистрировать его, убедитесь, что ключ еще не зарегистрирован.
u2f_error_5=Таймаут достигнут до того, как ваш ключ был прочитан. Перезагрузите, чтобы повторить попытку.
u2f_reload=Обновить
repository=Репозиторий
@ -130,7 +122,6 @@ federated_avatar_lookup=Включить федеративные аватары
federated_avatar_lookup_popup=Включите поиск федеративного аватара для использования службы с открытым исходным кодом на основе libravatar.
disable_registration=Отключить самостоятельную регистрацию
disable_registration_popup=Запретить самостоятельную регистрацию. Только администраторы смогут создавать новые учетные записи пользователей.
allow_only_external_registration_popup=Включить регистрацию только через сторонние сервисы.
openid_signin=Включение входа через OpenID
openid_signin_popup=Включение входа через OpenID.
openid_signup=Включить саморегистрацию OpenID
@ -463,13 +454,10 @@ then_enter_passcode=И введите пароль, показанный в пр
passcode_invalid=Неверный пароль. попробуйте снова.
twofa_enrolled=Для вашего аккаунта была включена двухфакторная аутентификация. Сохраните ваш scratch-токен (%s), он не сохраняется на сервере!
u2f_desc=Ключами безопасности являются аппаратные устройства, содержащие криптографические ключи. Они могут использоваться для двухфакторной аутентификации. Ключ безопасности должен поддерживать стандарт <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=Для использования ключей безопасности необходимо включить двухфакторную аутентификацию.
u2f_register_key=Добавить ключ безопасности
u2f_nickname=Имя пользователя
u2f_press_button=Нажмите кнопку на ключе безопасности, чтобы зарегистрировать его.
u2f_delete_key=Удалить ключ безопасности
u2f_delete_key_desc=Если вы удалите ключ безопасности, вы не сможете использовать его для входа. Вы уверены?
manage_account_links=Управление привязанными аккаунтами
manage_account_links_desc=Эти внешние аккаунты привязаны к вашему аккаунту Gitea.
@ -650,7 +638,6 @@ issues.new.open_milestone=Открыть этап
issues.new.closed_milestone=Завершенные этапы
issues.new.assignees=Назначенные
issues.new.clear_assignees=Убрать ответственных
issues.new.no_assignees=Никто не назначен
issues.no_ref=Не указана ветка или тэг
issues.create=Добавить задачу
issues.new_label=Новая метка
@ -1168,8 +1155,6 @@ branch.protected_deletion_failed=Ветка '%s' защищена. Её нель
topic.manage_topics=Редактировать тематические метки
topic.done=Сохранить
topic.count_prompt=Вы не можете выбрать более 25 тем
topic.format_prompt=Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов
[org]
org_name_holder=Название организации
@ -1274,8 +1259,6 @@ dashboard.operation_switch=Переключить
dashboard.operation_run=Запуск
dashboard.clean_unbind_oauth=Очистить список незавершённых авторизаций OAuth
dashboard.clean_unbind_oauth_success=Все незавершённые связи OAuth были удалены.
dashboard.delete_inactivate_accounts=Удалить все неактивированные учетные записи
dashboard.delete_inactivate_accounts_success=Все не активированные учетные записи были удалены.
dashboard.delete_repo_archives=Удаление всех архивов репозиториев
dashboard.delete_repo_archives_success=Все архивы репозиториев удалены.
dashboard.delete_missing_repos=Удалить все записи о репозиториях с отсутствующими файлами Git
@ -1483,7 +1466,6 @@ config.db_path=Путь
config.service_config=Сервисная конфигурация
config.register_email_confirm=Требуется подтверждение по электронной почте
config.disable_register=Отключить самостоятельную регистрацию
config.allow_only_external_registration=Включить регистрацию только через сторонние сервисы
config.enable_openid_signup=Включить cамостоятельную регистрацию OpenID
config.enable_openid_signin=Включение входа через OpenID
config.show_registration_button=Показать кнопку регистрации

View File

@ -32,16 +32,8 @@ twofa_scratch=Tvåfaktorsskrapkod
passcode=Kod
u2f_insert_key=Sätt i din säkerhetsnyckel
u2f_sign_in=Tryck på knappen på din säkerhetsnyckel. Om du inte kan hitta en knapp, dra ur och sätt i nyckeln igen.
u2f_press_button=Vänligen tryck på knappen på din säkerhetsnyckel…
u2f_use_twofa=Använd en tvåfaktorskod från din telefon
u2f_error=Vi kan inte läsa din säkerhetsnyckel!
u2f_unsupported_browser=Din webbläsare stödjer inte U2F-nycklar. Vänligen prova en annan webbläsare.
u2f_error_1=Ett okänt fel uppstod. Vänligen försök igen.
u2f_error_2=Kontrollera att du använder en krypterad anslutning (https://) och besöker korrekt länk.
u2f_error_3=Servern kunde inte hantera din förfrågan.
u2f_error_4=Den givna nyckeln är inte behörig för denna förfrågan. Om du försöker registrera den, se till att den inte redan är registrerad.
u2f_error_5=Timeout uppnådd innan nyckeln kunde bli läst. Vänligen ladda om sidan och för att försök igen.
u2f_reload=Ladda om
repository=Utvecklingskatalog
@ -129,7 +121,6 @@ disable_gravatar_popup=Inaktivera Gravatar- och avatarskällor från tredjepart.
federated_avatar_lookup_popup=Använd libravatar vid förenad uppslagning av avatarer.
disable_registration=Inaktivera Självregistrering
disable_registration_popup=Inaktivera självregistrering av användare. Endast administratörer kommer kunna skapa nya konton.
allow_only_external_registration_popup=Aktivera registrering enbart via externa tjänster.
openid_signin=Aktivera OpenID-inloggning
openid_signin_popup=Aktivera användarinloggning via OpenID.
openid_signup=Aktivera självregistrering genom OpenID
@ -462,13 +453,10 @@ then_enter_passcode=Och ange den lösenkod som visas i programmet:
passcode_invalid=Koden är ogiltig. Försök igen.
twofa_enrolled=Tvåfaktorsautentisering har aktiverats för ditt konto. Förvara din skrapkod (%s) på en säker plats eftersom den bara visas en gång!
u2f_desc=Säkerhetsnycklar är maskinvaruenheter som innehåller kryptografiska nycklar. De kan användas för tvåfaktorautentisering. Säkerhetsnyckeln måste dock stödja <a href="https://fidoalliance.org/">FIDO U2F</a> standarden.
u2f_require_twofa=Tvåfaktorautentisering måste aktiveras för att kunna använda säkerhetsnycklar.
u2f_register_key=Lägg till säkerhetsnyckel
u2f_nickname=Smeknamn
u2f_press_button=Tryck på knappen på din säkerhetsnyckel för att registrera den.
u2f_delete_key=Ta Bort Säkerhetsnyckel
u2f_delete_key_desc=Om du tar bort en säkerhetsnyckel kommer du inte kunna logga in med den längre. Är du säker?
manage_account_links=Hantera Länkade Konton
manage_account_links_desc=Dessa externa konton är länkade till ditt Gitea-konto.
@ -646,7 +634,6 @@ issues.new.open_milestone=Öppna Milstenar
issues.new.closed_milestone=Stängda Milstenar
issues.new.assignees=Tilldelade
issues.new.clear_assignees=Rensa tilldelade
issues.new.no_assignees=Ingen tilldelad
issues.no_ref=Ingen branch/Tag specificerad
issues.create=Skapa Ärende
issues.new_label=Ny etikett
@ -1127,8 +1114,6 @@ branch.protected_deletion_failed=Branch '%s' är skyddad. Den kan inte bli bortt
topic.manage_topics=Hantera ämnen
topic.done=Klar
topic.count_prompt=Du kan inte markera mer än 25 ämnen
topic.format_prompt=Ämnen måste starta med en bokstav eller nummer, kan inkludera bindestreck(-) och får inte vara längre än 35 tecken långt
[org]
org_name_holder=Organisationsnamn
@ -1229,8 +1214,6 @@ dashboard.operation_switch=Byt till
dashboard.operation_run=Kör
dashboard.clean_unbind_oauth=Rena obundna OAuth anslutningar
dashboard.clean_unbind_oauth_success=Alla obundna OAuth anslutningar har raderats.
dashboard.delete_inactivate_accounts=Ta bort alla inaktiva konton
dashboard.delete_inactivate_accounts_success=Alla inaktiva konton har tagits bort.
dashboard.delete_repo_archives=Ta bort alla utvecklingskatalogers arkiv
dashboard.delete_missing_repos=Ta bort alla utvecklingskataloger som saknar filer specifika för Git
dashboard.delete_missing_repos_success=Alla utvecklingskataloger som saknade sina Git-filer har tagits bort.
@ -1417,7 +1400,6 @@ config.db_path=Sökväg
config.service_config=Tjänstkonfiguration
config.register_email_confirm=Kräv mejlbekräftelse för att registrera
config.disable_register=Inaktivera självregistrering
config.allow_only_external_registration=Aktivera registrering enbart genom externa tjänster
config.enable_openid_signup=Aktivera självregistrering genom OpenID
config.enable_openid_signin=Aktivera OpenID-inloggning
config.show_registration_button=Visa registreringsknapp

View File

@ -32,16 +32,8 @@ twofa_scratch=Двофакторний одноразовий пароль
passcode=Код доступу
u2f_insert_key=Вставте ключ безпеки
u2f_sign_in=Натисніть кнопку на ключі безпеки. Якщо не вдається знайти кнопки, повторно вставте ключ.
u2f_press_button=Будь ласка, натисніть кнопку на ключі захисту...
u2f_use_twofa=Використовуйте дво-факторний код з вашого телефону
u2f_error=Неможливо прочитати ваш ключ безпеки!
u2f_unsupported_browser=Ваш браузер не підтримує U2F ключі. Будь ласка, спробуйте інший браузер.
u2f_error_1=Сталася невідома помилка. Спробуйте ще раз.
u2f_error_2=Переконайтеся, що ви використовуєте зашифроване з'єднання (https://) та відвідуєте правильну URL-адресу.
u2f_error_3=Сервер не може обробити, ваш запит.
u2f_error_4=Представлений ключ не дає право на цей запит. Якщо спробуєте зареєструвати його, переконайтеся, що ключ ще не зареєстровано.
u2f_error_5=Таймаут досягнуто до того, як ваш ключ можна буде прочитати. Перезавантажте, щоб повторити спробу.
u2f_reload=Оновити
repository=Репозиторій
@ -130,7 +122,6 @@ federated_avatar_lookup=Увімкнути федеративні аватари
federated_avatar_lookup_popup=Увімкнути зовнішний Аватар за допомогою Libravatar.
disable_registration=Вимкнути самостійну реєстрацію
disable_registration_popup=Вимкнути самостійну реєстрацію користувачів, тільки адміністратор може створювати нові облікові записи.
allow_only_external_registration_popup=Включити реєстрацію тільки через зовнішні сервіси.
openid_signin=Увімкнути реєстрацію за допомогою OpenID
openid_signin_popup=Увімкнути вхід за допомогою OpenID.
openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID
@ -463,13 +454,10 @@ then_enter_passcode=І введіть пароль, який відобража
passcode_invalid=Некоректний пароль. Спробуй ще раз.
twofa_enrolled=Для вашого облікового запису було включена двофакторна автентифікація. Зберігайте свій scratch-токен (%s) у безпечному місці, оскільки він показується лише один раз!
u2f_desc=Ключами безпеки є апаратні пристрої, що містять криптографічні ключі. Вони можуть використовуватися для двофакторної автентифікації. Ключ безпеки повинен підтримувати стандарт <a href="https://fidoalliance.org/">FIDO U2F</a>.
u2f_require_twofa=Для використання ключів безпеки необхідно зареєструвати двофакторну аутентифікацію.
u2f_register_key=Додати ключ безпеки
u2f_nickname=Псевдонім
u2f_press_button=Натисніть кнопку на ключі безпеки, щоб зареєструвати його.
u2f_delete_key=Видалити ключ безпеки
u2f_delete_key_desc=Якщо ви видалите ключ безпеки, ви не зможете використати його для входу. Ти впевнені?
manage_account_links=Керування обліковими записами
manage_account_links_desc=Ці зовнішні акаунти прив'язані до вашого аккаунту Gitea.
@ -650,7 +638,6 @@ issues.new.open_milestone=Активні етапи
issues.new.closed_milestone=Закриті етапи
issues.new.assignees=Виконавеці
issues.new.clear_assignees=Прибрати виконавеців
issues.new.no_assignees=Ніхто не призначений
issues.no_ref=Не вказана гілка або тег
issues.create=Створити проблему
issues.new_label=Нова мітка
@ -781,6 +768,14 @@ issues.due_date_added=додав(ла) дату завершення %s %s
issues.due_date_modified=термін змінено з %s %s на %s
issues.due_date_remove=видалив(ла) дату завершення %s %s
issues.due_date_overdue=Прострочено
issues.dependency.title=Залежності
issues.dependency.issue_no_dependencies=Ця проблема в даний час не має залежностей.
issues.dependency.pr_no_dependencies=Цей запит на злиття в даний час не має залежностей.
issues.dependency.cancel=Відмінити
issues.dependency.remove=Видалити
issues.dependency.added_dependency=<div class="notranslate"> 0%</div>[2]s</a> додано нову залежність %[3]s'
issues.dependency.removed_dependency=<div class="notranslate"> 0%</div>[2]s</a> видалено залежність %[3]s'
issues.dependency.blocks_short=Блоки
pulls.desc=Увімкнути запити на злиття та інтерфейс узгодження правок.
pulls.new=Новий запит на злиття
@ -1168,8 +1163,6 @@ branch.protected_deletion_failed=Гілка '%s' захищена. Її не м
topic.manage_topics=Керувати тематичними мітками
topic.done=Готово
topic.count_prompt=Ви не можете вибрати більше 25 тем
topic.format_prompt=Теми мають починатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів
[org]
org_name_holder=Назва організації
@ -1274,8 +1267,6 @@ dashboard.operation_switch=Перемкнути
dashboard.operation_run=Запустити
dashboard.clean_unbind_oauth=Очистити список незавершених авторизацій OAuth
dashboard.clean_unbind_oauth_success=Всі незавершені зв'язки OAuth були видалені.
dashboard.delete_inactivate_accounts=Видалити всі неактивовані облікові записи
dashboard.delete_inactivate_accounts_success=Усі неактивовані облікові записи успішно видалено.
dashboard.delete_repo_archives=Видалити всі архіви репозиторіїв
dashboard.delete_repo_archives_success=Всі архіви репозиторіїв були видалені.
dashboard.delete_missing_repos=Видалити всі записи про репозиторії з відсутніми файлами Git
@ -1483,7 +1474,6 @@ config.db_path=Шлях
config.service_config=Конфігурація сервісу
config.register_email_confirm=Потрібно підтвердити електронну пошту для реєстрації
config.disable_register=Вимкнути самостійну реєстрацію
config.allow_only_external_registration=Включити реєстрацію тільки через сторонні сервіси
config.enable_openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID
config.enable_openid_signin=Увімкнути реєстрацію за допомогою OpenID
config.show_registration_button=Показувати кнопку "Реєстрація

View File

@ -32,16 +32,8 @@ twofa_scratch=两步验证口令
passcode=验证码
u2f_insert_key=插入安全密钥
u2f_sign_in=按下安全密钥上的按钮。如果找不到按钮, 请重新插入。
u2f_press_button=请按下安全密钥上的按钮。
u2f_use_twofa=使用来自你手机中的两步验证码
u2f_error=没有找到你的安全密钥!
u2f_unsupported_browser=您的浏览器不支持 U2F 密钥。请尝试其他浏览器。
u2f_error_1=发生未知错误。请重试。
u2f_error_2=请确保您使用的是加密连接 (https://) 并访问正确的 URL。
u2f_error_3=服务器无法执行您的请求。
u2f_error_4=所提交的密钥不符合此请求。如果您尝试注册它, 请确保该密钥尚未注册。
u2f_error_5=在读取到密钥之前超时。请重新加载以重试。
u2f_reload=重新加载
repository=仓库
@ -130,7 +122,6 @@ federated_avatar_lookup=启用 Federated 头像
federated_avatar_lookup_popup=启用 Federated Avatars 查找以使用开源的 Libravatar 服务。
disable_registration=禁止用户自助注册
disable_registration_popup=禁用用户自助注册。只有管理员才能创建新的用户帐户。
allow_only_external_registration_popup=仅允许通过外部服务注册。
openid_signin=启用 OpenID 登录
openid_signin_popup=启用通过 OpenID 登录
openid_signup=启用 OpenID 自助注册
@ -463,13 +454,10 @@ then_enter_passcode=并输入应用程序中显示的密码:
passcode_invalid=密码不正确。再试一次。
twofa_enrolled=你的账号已经启用了两步验证。请保存初始令牌(%s到一个安全的地方此令牌仅当前显示一次。
u2f_desc=安全密钥是包含加密算法的硬件设备。它们可以用于两步验证。安全密钥必须支持 <a href="https://fidoalliance.org/">FIDO U2F</a> 标准。
u2f_require_twofa=必须开启两步验证才能使用安全密钥。
u2f_register_key=添加安全密钥
u2f_nickname=昵称
u2f_press_button=按安全密钥上的按钮进行注册。
u2f_delete_key=移除安全密钥
u2f_delete_key_desc=如果移除安全密钥, 则无法再使用它登录。是否确定?
manage_account_links=管理绑定过的账号
manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。
@ -650,7 +638,6 @@ issues.new.open_milestone=开启中的里程碑
issues.new.closed_milestone=已关闭的里程碑
issues.new.assignees=指派成员
issues.new.clear_assignees=取消指派成员
issues.new.no_assignees=未指派成员
issues.no_ref=分支/标记未指定
issues.create=创建工单
issues.new_label=创建标签
@ -1167,8 +1154,6 @@ branch.protected_deletion_failed=分支 '%s' 已被保护,不可删除。
topic.manage_topics=管理主题
topic.done=保存
topic.count_prompt=您最多选择25个主题
topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 (-)并且长度不得超过35个字符
[org]
org_name_holder=组织名称
@ -1273,8 +1258,6 @@ dashboard.operation_switch=开关
dashboard.operation_run=执行
dashboard.clean_unbind_oauth=清理未绑定的 OAuth 连接
dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth 连接已被删除。
dashboard.delete_inactivate_accounts=删除所有未激活的帐户
dashboard.delete_inactivate_accounts_success=所有未激活的帐户都已删除。
dashboard.delete_repo_archives=删除所有仓库存档
dashboard.delete_repo_archives_success=所有仓库存档清除成功!
dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库
@ -1482,7 +1465,6 @@ config.db_path=数据库路径
config.service_config=服务配置
config.register_email_confirm=需要电子邮件确认注册
config.disable_register=禁止用户注册
config.allow_only_external_registration=仅允许通过外部服务注册
config.enable_openid_signup=启用 OpenID 自注册
config.enable_openid_signin=启用 OpenID 登录
config.show_registration_button=显示注册按钮

View File

@ -32,16 +32,8 @@ twofa_scratch=兩步驟驗證備用碼
passcode=驗證碼
u2f_insert_key=插入安全金鑰
u2f_sign_in=按下安全金鑰上的按鈕。如果找不到按鈕, 請重新插入。
u2f_press_button=請按下安全金鑰上的按鈕…
u2f_use_twofa=使用來自手機的兩步驟驗證碼
u2f_error=找不到您的安全金鑰!
u2f_unsupported_browser=您的瀏覽器不支援 U2F 二步驟驗證技術。請嘗試其他瀏覽器。
u2f_error_1=發生未知的錯誤,請重試。
u2f_error_2=請確保您使用的是加密連接 (https://) 並且 URL 是正確的。
u2f_error_3=伺服器無法執行您的請求。
u2f_error_4=所提交的金鑰不符合此請求。如果您嘗試註冊它, 請確保該金鑰尚未註冊。
u2f_error_5=在讀取金鑰之前已逾時,請重新載入以重試。
u2f_reload=重新載入
repository=儲存庫
@ -129,7 +121,6 @@ federated_avatar_lookup=開啟聯合大頭貼
federated_avatar_lookup_popup=開啟聯合頭像查詢並使用基於開放源碼的 libravatar 服務
disable_registration=關閉註冊功能
disable_registration_popup=關閉註冊功能,只有管理員可以新增帳號。
allow_only_external_registration_popup=僅允許通過外部服務註冊。
openid_signin=啟用 OpenID 登入
openid_signin_popup=啟用 OpenID 登入
openid_signup=啟用 OpenID 註冊
@ -432,13 +423,10 @@ or_enter_secret=或者輸入密碼: %s
then_enter_passcode=然後輸入應用程序中顯示的驗證碼:
passcode_invalid=無效的驗證碼,請重試。
u2f_desc=安全密鑰是包含加密密鑰的硬體設備。 它們可以用於兩步驟認證。 安全密鑰必須符合 <a href="https://fidoalliance.org/">FIDO U2F</a> 標準。
u2f_require_twofa=必須先開啟兩步驟驗證才能使用安全密鑰。
u2f_register_key=新增安全密鑰
u2f_nickname=暱稱
u2f_press_button=按下安全密鑰上的密碼進行註冊。
u2f_delete_key=移除安全密鑰
u2f_delete_key_desc=如果您移除安全密鑰,則無法再使用它登錄。 確定嗎?
manage_account_links=管理已連結的帳號
manage_account_links_desc=這些外部帳號與您的 Gitea 帳號相關聯。
@ -591,7 +579,6 @@ issues.new.open_milestone=開啟中的里程碑
issues.new.closed_milestone=已關閉的里程碑
issues.new.assignees=指派成員
issues.new.clear_assignees=取消指派成員
issues.new.no_assignees=未指派成員
issues.no_ref=未指定分支或標籤
issues.create=建立問題
issues.new_label=建立標籤
@ -1260,7 +1247,6 @@ config.db_path=資料庫路徑
config.service_config=服務設定
config.register_email_confirm=要求註冊時確認電子郵件
config.disable_register=關閉註冊功能
config.allow_only_external_registration=僅允許通過外部服務註冊
config.enable_openid_signup=啟用 OpenID 註冊
config.enable_openid_signin=啟用 OpenID 登入
config.show_registration_button=顯示註冊按鈕

View File

@ -1769,6 +1769,7 @@ $(document).ready(function () {
initTopicbar();
initU2FAuth();
initU2FRegister();
initIssueList();
// Repo clone url.
if ($('#repo-clone-url').length > 0) {
@ -2488,3 +2489,41 @@ function updateDeadline(deadlineString) {
}
});
}
function deleteDependencyModal(id, type) {
$('.remove-dependency')
.modal({
closable: false,
duration: 200,
onApprove: function () {
$('#removeDependencyID').val(id);
$('#dependencyType').val(type);
$('#removeDependencyForm').submit();
}
}).modal('show')
;
}
function initIssueList() {
var repolink = $('#repolink').val();
$('.new-dependency-drop-list')
.dropdown({
apiSettings: {
url: '/api/v1/repos' + repolink + '/issues?q={query}',
onResponse: function(response) {
var filteredResponse = {'success': true, 'results': []};
// Parse the response from the api to work with our dropdown
$.each(response, function(index, issue) {
filteredResponse.results.push({
'name' : '#' + issue.number + '&nbsp;' + issue.title,
'value' : issue.id
});
});
return filteredResponse;
},
},
fullTextSearch: true
})
;
}

View File

@ -11,122 +11,122 @@
</thead>
<tbody>
<tr>
<td><a href="/vendor/plugins/jquery.areyousure/jquery.are-you-sure.js">jquery.are-you-sure.js</a></td>
<td><a href="./plugins/jquery.areyousure/jquery.are-you-sure.js">jquery.are-you-sure.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/codedance/jquery.AreYouSure/archive/1.9.0.tar.gz">jquery.areyousure-1.9.0.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/jquery/jquery.min.js">jquery.min.js</a></td>
<td><a href="./plugins/jquery/jquery.min.js">jquery.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://code.jquery.com/jquery-1.11.3.min.js">jquery-1.11.3.min.js</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/semantic/semantic.min.js">semantic.min.js</a></td>
<td><a href="./plugins/semantic/semantic.min.js">semantic.min.js</a></td>
<td><a href="https://semantic-ui.mit-license.org/">Expat</a></td>
<td><a href="https://github.com/Semantic-Org/Semantic-UI/archive/2.3.1.tar.gz">semantic-UI-2.3.1.tar.gz</a></td>
</tr>
<tr>
<td><a href="/js/index.js">index.js</a></td>
<td><a href="../js/index.js">index.js</a></td>
<td><a href="https://github.com/go-gitea/gitea/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/go-gitea/gitea/tree/master/public/js">index.js</a></td>
</tr>
<tr>
<td><a href="/js/draw.js">draw.js</a></td>
<td><a href="../js/draw.js">draw.js</a></td>
<td><a href="https://github.com/go-gitea/gitea/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/go-gitea/gitea/tree/master/public/js">draw.js</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/clipboard/clipboard.min.js">clipboard.min.js</a></td>
<td><a href="./plugins/clipboard/clipboard.min.js">clipboard.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/zenorocha/clipboard.js/archive/v1.5.9.tar.gz">clipboard-1.5.9.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/gitgraph/gitgraph.js">gitgraph.js</a></td>
<td><a href="./plugins/gitgraph/gitgraph.js">gitgraph.js</a></td>
<td><a href="https://github.com/bluef/gitgraph.js/blob/master/LICENSE">BSD 3-Clause</a></td>
<td><a href="https://github.com/bluef/gitgraph.js">gitgraph.js-latest</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/vue/vue.min.js">vue.min.js</a></td>
<td><a href="./plugins/vue/vue.min.js">vue.min.js</a></td>
<td><a href="https://github.com/vuejs/vue/blob/dev/LICENSE">Expat</a></td>
<td><a href="https://github.com/vuejs/vue/archive/v2.1.10.tar.gz">vue.js-v2.1.10.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/emojify/emojify.min.js">emojify.min.js</a></td>
<td><a href="./plugins/emojify/emojify.min.js">emojify.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/Ranks/emojify.js/archive/1.1.0.tar.gz">emojify-1.1.0.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/cssrelpreload/loadCSS.min.js">loadCSS.min.js</a></td>
<td><a href="./plugins/cssrelpreload/loadCSS.min.js">loadCSS.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/filamentgroup/loadCSS/archive/v1.3.1.tar.gz">loadCSS-1.3.1.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/cssrelpreload/cssrelpreload.min.js">cssrelpreload.min.js</a></td>
<td><a href="./plugins/cssrelpreload/cssrelpreload.min.js">cssrelpreload.min.js</a></td>
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td>
<td><a href="https://github.com/filamentgroup/loadCSS/archive/v1.3.1.tar.gz">loadCSS-1.3.1.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/dropzone/dropzone.js">dropzone.js</a></td>
<td><a href="./plugins/dropzone/dropzone.js">dropzone.js</a></td>
<td><a href="https://github.com/enyo/dropzone/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/enyo/dropzone/archive/v4.2.0.tar.gz">dropzone.js-4.2.0.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/highlight/highlight.pack.js">highlight.pack.js</a></td>
<td><a href="./plugins/highlight/highlight.pack.js">highlight.pack.js</a></td>
<td><a href="https://github.com/isagalaev/highlight.js/blob/master/LICENSE">BSD 3-Clause</a></td>
<td><a href="https://github.com/isagalaev/highlight.js/archive/9.6.0.tar.gz">highlight.js-9.6.0.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/jquery.datetimepicker/jquery.datetimepicker.js">jquery.datetimepicker.js</a></td>
<td><a href="./plugins/jquery.datetimepicker/jquery.datetimepicker.js">jquery.datetimepicker.js</a></td>
<td><a href="https://github.com/xdan/datetimepicker/blob/master/MIT-LICENSE.txt">Expat</a></td>
<td><a href="https://github.com/xdan/datetimepicker/archive/2.4.5.tar.gz">jquery.datetimepicker-2.4.5.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/jquery.minicolors/jquery.minicolors.min.js">jquery.minicolors.min.js</a></td>
<td><a href="./plugins/jquery.minicolors/jquery.minicolors.min.js">jquery.minicolors.min.js</a></td>
<td><a href="https://github.com/claviska/jquery-minicolors/blob/master/LICENSE.md">Expat</a></td>
<td><a href="https://github.com/claviska/jquery-minicolors/archive/2.2.3.tar.gz">jquery.minicolors-2.2.3.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/codemirror/addon/mode/loadmode.js">loadmode.js</a></td>
<td><a href="./plugins/codemirror/addon/mode/loadmode.js">loadmode.js</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/archive/5.17.0.tar.gz">codemirror-5.17.0.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/codemirror/mode/meta.js">meta.js</a></td>
<td><a href="./plugins/codemirror/mode/meta.js">meta.js</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/codemirror/CodeMirror/archive/5.17.0.tar.gz">codemirror-5.17.0.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/simplemde/simplemde.min.js">simplemde.min.js</a></td>
<td><a href="./plugins/simplemde/simplemde.min.js">simplemde.min.js</a></td>
<td><a href="https://github.com/sparksuite/simplemde-markdown-editor/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/NextStepWebs/simplemde-markdown-editor/archive/1.10.1.tar.gz">simplemde-markdown-editor-1.10.1.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/pdfjs/">pdf.js</a></td>
<td><a href="./plugins/pdfjs/">pdf.js</a></td>
<td><a href="https://github.com/mozilla/pdf.js/blob/master/LICENSE">Apache-2.0-only</a></td>
<td><a href="https://github.com/mozilla/pdf.js/archive/v1.4.20.tar.gz">pdf.js-v1.4.20.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/plugins/u2f/">u2f-api</a></td>
<td><a href="./plugins/u2f/">u2f-api</a></td>
<td><a href="https://github.com/go-gitea/u2f-api/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/go-gitea/u2f-api/archive/v1.0.8.zip">u2f-api-1.0.8.zip</a></td>
</tr>
<tr>
<td><a href="/vendor/assets/font-awesome/fonts/">font-awesome - fonts</a></td>
<td><a href="./assets/font-awesome/fonts/">font-awesome - fonts</a></td>
<td><a href="http://fontawesome.io/license/">OFL</a></td>
<td><a href="http://fontawesome.io/assets/font-awesome-4.6.0.zip">font-awesome-4.6.0.zip</a></td>
</tr>
<tr>
<td><a href="/vendor/assets/font-awesome/css/">font-awesome - code</a></td>
<td><a href="./assets/font-awesome/css/">font-awesome - code</a></td>
<td><a href="http://fontawesome.io/license/">Expat</a></td>
<td><a href="http://fontawesome.io/assets/font-awesome-4.6.0.zip">font-awesome-4.6.0.zip</a></td>
</tr>
<tr>
<td><a href="/vendor/assets/octicons/">octicons</a></td>
<td><a href="./assets/octicons/">octicons</a></td>
<td><a href="https://github.com/primer/octicons/blob/master/LICENSE">Expat</a></td>
<td><a href="https://github.com/primer/octicons/archive/v4.3.0.tar.gz">octicons-v4.3.0.tar.gz</a></td>
</tr>
<tr>
<td><a href="/vendor/assets/swagger-ui/">swagger-ui</a></td>
<td><a href="./assets/swagger-ui/">swagger-ui</a></td>
<td><a href="https://github.com/swagger-api/swagger-ui/blob/master/LICENSE">Apache-2.0</a></td>
<td><a href="https://github.com/swagger-api/swagger-ui/archive/v3.0.4.tar.gz">swagger-ui-v3.0.4.tar.gz</a></td>
</tr>

View File

@ -7,6 +7,7 @@ package repo
import (
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models"
@ -208,6 +209,10 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
if form.Closed {
if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, true); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
}
ctx.Error(500, "ChangeStatus", err)
return
}
@ -325,6 +330,10 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
}
if form.State != nil {
if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
}
ctx.Error(500, "ChangeStatus", err)
return
}

View File

@ -6,6 +6,7 @@ package repo
import (
"fmt"
"net/http"
"strings"
"code.gitea.io/git"
@ -378,6 +379,10 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
}
if form.State != nil {
if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
return
}
ctx.Error(500, "ChangeStatus", err)
return
}

View File

@ -10,6 +10,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
@ -302,6 +303,9 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.
}
ctx.Data["Branches"] = brs
// Contains true if the user can create issue dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
return labels
}
@ -665,6 +669,9 @@ func ViewIssue(ctx *context.Context) {
}
}
// Check if the user can use the dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
// Render comments and and fetch participants.
participants[0] = issue.Poster
for _, comment = range issue.Comments {
@ -721,6 +728,11 @@ func ViewIssue(ctx *context.Context) {
ctx.ServerError("LoadAssigneeUser", err)
return
}
} else if comment.Type == models.CommentTypeRemoveDependency || comment.Type == models.CommentTypeAddDependency {
if err = comment.LoadDepIssueDetails(); err != nil {
ctx.ServerError("LoadDepIssueDetails", err)
return
}
}
}
@ -774,6 +786,10 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["IsPullBranchDeletable"] = canDelete && pull.HeadRepo != nil && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch)
}
// Get Dependencies
ctx.Data["BlockedByDependencies"], err = issue.BlockedByDependencies()
ctx.Data["BlockingDependencies"], err = issue.BlockingDependencies()
ctx.Data["Participants"] = participants
ctx.Data["NumParticipants"] = len(participants)
ctx.Data["Issue"] = issue
@ -971,6 +987,12 @@ func UpdateIssueStatus(ctx *context.Context) {
}
for _, issue := range issues {
if err := issue.ChangeStatus(ctx.User, issue.Repo, isClosed); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.JSON(http.StatusPreconditionFailed, map[string]interface{}{
"error": "cannot close this issue because it still has open dependencies",
})
return
}
ctx.ServerError("ChangeStatus", err)
return
}
@ -1034,6 +1056,17 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
} else {
if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil {
log.Error(4, "ChangeStatus: %v", err)
if models.IsErrDependenciesLeft(err) {
if issue.IsPull {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
} else {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
}
return
}
} else {
log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)

View File

@ -0,0 +1,119 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
)
// AddDependency adds new dependencies
func AddDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("newDependency")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
ctx.ServerError("GetIssueByIndex", err)
return
}
// Redirect
defer ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
// Dependency
dep, err := models.GetIssueByID(depID)
if err != nil {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_issue_not_exist"))
return
}
// Check if both issues are in the same repo
if issue.RepoID != dep.RepoID {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_not_same_repo"))
return
}
// Check if issue and dependency is the same
if dep.Index == issueIndex {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_same_issue"))
return
}
err = models.CreateIssueDependency(ctx.User, issue, dep)
if err != nil {
if models.IsErrDependencyExists(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_exists"))
return
} else if models.IsErrCircularDependency(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_cannot_create_circular"))
return
} else {
ctx.ServerError("CreateOrUpdateIssueDependency", err)
return
}
}
}
// RemoveDependency removes the dependency
func RemoveDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("removeDependencyID")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
ctx.ServerError("GetIssueByIndex", err)
return
}
// Redirect
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
// Dependency Type
depTypeStr := ctx.Req.PostForm.Get("dependencyType")
var depType models.DependencyType
switch depTypeStr {
case "blockedBy":
depType = models.DependencyTypeBlockedBy
case "blocking":
depType = models.DependencyTypeBlocking
default:
ctx.Error(http.StatusBadRequest, "GetDependecyType")
return
}
// Dependency
dep, err := models.GetIssueByID(depID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
if err = models.RemoveIssueDependency(ctx.User, issue, dep, depType); err != nil {
if models.IsErrDependencyNotExists(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_not_exist"))
return
}
ctx.ServerError("RemoveIssueDependency", err)
return
}
}

View File

@ -524,6 +524,18 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
pr.Issue = issue
pr.Issue.Repo = ctx.Repo.Repository
noDeps, err := models.IssueNoDependenciesLeft(issue)
if err != nil {
return
}
if !noDeps {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
return
}
if err = pr.Merge(ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))

View File

@ -202,6 +202,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
Config: &models.IssuesConfig{
EnableTimetracker: form.EnableTimetracker,
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
EnableDependencies: form.EnableIssueDependencies,
},
})
}

View File

@ -527,6 +527,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/title", repo.UpdateIssueTitle)
m.Post("/content", repo.UpdateIssueContent)
m.Post("/watch", repo.IssueWatch)
m.Group("/dependency", func() {
m.Post("/add", repo.AddDependency)
m.Post("/delete", repo.RemoveDependency)
})
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
m.Group("/times", func() {
m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -320,7 +321,7 @@ func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthFo
handleSignInFull(ctx, u, remember, false)
ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}

View File

@ -150,6 +150,8 @@
{{end}}
<dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt>
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_enable_dependencies"}}</dt>
<dd><i class="fa fa{{if .Service.DefaultEnableDependencies}}-check{{end}}-square-o"></i></dd>
<div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.active_code_lives"}}</dt>
<dd>{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd>

View File

@ -72,7 +72,7 @@
{{.i18n.Tr "repo.branch.delete_html"}} <span class="branch-name"></span>
</div>
<div class="content">
<p>{{.i18n.Tr "repo.branch.delete_desc"}}</p>
<p>{{.i18n.Tr "repo.branch.delete_desc" | Str2html}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

View File

@ -137,7 +137,7 @@
{{.i18n.Tr "repo.branch.delete" .HeadTarget }}
</div>
<div class="content">
<p>{{.i18n.Tr "repo.branch.delete_desc"}}</p>
<p>{{.i18n.Tr "repo.branch.delete_desc" | Str2html}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

View File

@ -1,7 +1,7 @@
{{range .Issue.Comments}}
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }}
<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE -->
<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY -->
{{if eq .Type 0}}
<div class="comment" id="{{.HashTag}}">
<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
@ -65,7 +65,6 @@
{{end}}
</div>
</div>
{{else if eq .Type 1}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
@ -233,5 +232,33 @@
{{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}}
</span>
</div>
{{end}}
{{else if eq .Type 19}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey">
{{$.i18n.Tr "repo.issues.dependency.added_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}}
</span>
<div class="detail">
<span class="octicon octicon-plus"></span>
<span class="text grey"><a href="{{$.RepoLink}}/issues/{{.DependentIssue.Index}}">#{{.DependentIssue.Index}} {{.DependentIssue.Title}}</a></span>
</div>
</div>
{{else if eq .Type 20}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey">
{{$.i18n.Tr "repo.issues.dependency.removed_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}}
</span>
<div class="detail">
<span class="text grey octicon octicon-trashcan"></span>
<span class="text grey"><a href="{{$.RepoLink}}/issues/{{.DependentIssue.Index}}">#{{.DependentIssue.Index}} {{.DependentIssue.Title}}</a></span>
</div>
</div>
{{end}}
{{end}}

View File

@ -249,5 +249,142 @@
</div>
{{end}}
</div>
{{if .Repository.IsDependenciesEnabled}}
<div class="ui divider"></div>
<div class="ui depending">
<span class="text"><strong>{{.i18n.Tr "repo.issues.dependency.title"}}</strong></span>
<br>
{{if .BlockedByDependencies}}
<span class="text" data-tooltip="{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.issue_closing_blockedby"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.pr_closing_blockedby"}}
{{end}}" data-inverted="">
{{.i18n.Tr "repo.issues.dependency.blocked_by_short"}}:
</span>
<div class="ui relaxed divided list">
{{range .BlockedByDependencies}}
<div class="item">
<div class="right floated content">
{{if $.CanCreateIssueDependencies}}
<a class="delete-dependency-button" onclick="deleteDependencyModal({{.ID}}, 'blockedBy');">
<i class="delete icon text red"></i>
</a>
{{end}}
{{if .IsClosed}}
<div class="ui red mini label">
<i class="octicon octicon-issue-closed"></i>
</div>
{{else}}
<div class="ui green mini label">
<i class="octicon octicon-issue-opened"></i>
</div>
{{end}}
</div>
<div class="ui black label">#{{.Index}}</div>
<a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a>
</div>
{{end}}
</div>
{{end}}
{{if .BlockingDependencies}}
<span class="text" data-tooltip="{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.pr_close_blocks"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.issue_close_blocks"}}
{{end}}" data-inverted="">
{{.i18n.Tr "repo.issues.dependency.blocks_short"}}:
</span>
<div class="ui relaxed divided list">
{{range .BlockingDependencies}}
<div class="item">
<div class="right floated content">
{{if $.CanCreateIssueDependencies}}
<a class="delete-dependency-button" onclick="deleteDependencyModal({{.ID}}, 'blocking');">
<i class="delete icon text red"></i>
</a>
{{end}}
{{if .IsClosed}}
<div class="ui red tiny label">
<i class="octicon octicon-issue-closed"></i>
</div>
{{else}}
<div class="ui green mini label">
<i class="octicon octicon-issue-opened"></i>
</div>
{{end}}
</div>
<div class="ui black label">#{{.Index}}</div>
<a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a>
</div>
{{end}}
</div>
{{end}}
{{if (and (not .BlockedByDependencies) (not .BlockingDependencies))}}
<p>{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.pr_no_dependencies"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.issue_no_dependencies"}}
{{end}}</p>
{{end}}
{{if .CanCreateIssueDependencies}}
<div>
<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/dependency/add" id="addDependencyForm">
{{$.CsrfTokenHtml}}
<div class="ui fluid action input">
<div class="ui search selection dropdown new-dependency-drop-list" style="min-width: 13.9rem;border-radius: 4px 0 0 4px;border-right: 0;white-space: nowrap;">
<input name="newDependency" type="hidden">
<i class="dropdown icon"></i>
<input type="text" class="search">
<div class="default text">{{.i18n.Tr "repo.issues.dependency.add"}}</div>
</div>
<button class="ui green icon button">
<i class="plus icon"></i>
</button>
</div>
</form>
</div>
{{end}}
</div>
</div>
</div>
{{if .CanCreateIssueDependencies}}
<input type="hidden" id="repolink" value="{{$.RepoLink}}">
<!-- I know, there is probably a better way to do this -->
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/>
<div class="ui basic modal remove-dependency">
<div class="ui icon header">
<i class="trash icon"></i>
{{.i18n.Tr "repo.issues.dependency.remove_header"}}
</div>
<div class="content">
<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/dependency/delete" id="removeDependencyForm">
{{$.CsrfTokenHtml}}
<input type="hidden" value="" name="removeDependencyID" id="removeDependencyID"/>
<input type="hidden" value="" name="dependencyType" id="dependencyType"/>
</form>
<p>{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.dependency.pr_remove_text"}}
{{else}}
{{.i18n.Tr "repo.issues.dependency.issue_remove_text"}}
{{end}}</p>
</div>
<div class="actions">
<div class="ui basic red cancel inverted button">
<i class="remove icon"></i>
{{.i18n.Tr "repo.issues.dependency.cancel"}}
</div>
<div class="ui basic green ok inverted button">
<i class="checkmark icon"></i>
{{.i18n.Tr "repo.issues.dependency.remove"}}
</div>
</div>
</div>
{{end}}
{{end}}

View File

@ -153,6 +153,12 @@
</div>
</div>
{{end}}
<div class="field">
<div class="ui checkbox">
<input name="enable_issue_dependencies" type="checkbox" {{if (.Repository.IsDependenciesEnabled)}}checked{{end}}>
<label>{{.i18n.Tr "repo.issues.dependency.setting"}}</label>
</div>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">

View File

@ -4,6 +4,10 @@
package builder
import (
"fmt"
)
type optype byte
const (
@ -29,6 +33,9 @@ type Builder struct {
joins []join
inserts Eq
updates []Eq
orderBy string
groupBy string
having string
}
// Select creates a select Builder
@ -67,6 +74,11 @@ func (b *Builder) From(tableName string) *Builder {
return b
}
// TableName returns the table name
func (b *Builder) TableName() string {
return b.tableName
}
// Into sets insert table name
func (b *Builder) Into(tableName string) *Builder {
b.tableName = tableName
@ -178,6 +190,33 @@ func (b *Builder) ToSQL() (string, []interface{}, error) {
return w.writer.String(), w.args, nil
}
// ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix
func ConvertPlaceholder(sql, prefix string) (string, error) {
buf := StringBuilder{}
var j, start = 0, 0
for i := 0; i < len(sql); i++ {
if sql[i] == '?' {
_, err := buf.WriteString(sql[start:i])
if err != nil {
return "", err
}
start = i + 1
_, err = buf.WriteString(prefix)
if err != nil {
return "", err
}
j = j + 1
_, err = buf.WriteString(fmt.Sprintf("%d", j))
if err != nil {
return "", err
}
}
}
return buf.String(), nil
}
// ToSQL convert a builder or condtions to SQL and args
func ToSQL(cond interface{}) (string, []interface{}, error) {
switch cond.(type) {

View File

@ -15,7 +15,7 @@ func (b *Builder) insertWriteTo(w Writer) error {
return errors.New("no table indicated")
}
if len(b.inserts) <= 0 {
return errors.New("no column to be update")
return errors.New("no column to be insert")
}
if _, err := fmt.Fprintf(w, "INSERT INTO %s (", b.tableName); err != nil {
@ -26,7 +26,9 @@ func (b *Builder) insertWriteTo(w Writer) error {
var bs []byte
var valBuffer = bytes.NewBuffer(bs)
var i = 0
for col, value := range b.inserts {
for _, col := range b.inserts.sortedKeys() {
value := b.inserts[col]
fmt.Fprint(w, col)
if e, ok := value.(expr); ok {
fmt.Fprint(valBuffer, e.sql)

View File

@ -34,24 +34,65 @@ func (b *Builder) selectWriteTo(w Writer) error {
}
}
if _, err := fmt.Fprintf(w, " FROM %s", b.tableName); err != nil {
if _, err := fmt.Fprint(w, " FROM ", b.tableName); err != nil {
return err
}
for _, v := range b.joins {
fmt.Fprintf(w, " %s JOIN %s ON ", v.joinType, v.joinTable)
if _, err := fmt.Fprintf(w, " %s JOIN %s ON ", v.joinType, v.joinTable); err != nil {
return err
}
if err := v.joinCond.WriteTo(w); err != nil {
return err
}
}
if !b.cond.IsValid() {
return nil
if b.cond.IsValid() {
if _, err := fmt.Fprint(w, " WHERE "); err != nil {
return err
}
if err := b.cond.WriteTo(w); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, " WHERE "); err != nil {
return err
if len(b.groupBy) > 0 {
if _, err := fmt.Fprint(w, " GROUP BY ", b.groupBy); err != nil {
return err
}
}
return b.cond.WriteTo(w)
if len(b.having) > 0 {
if _, err := fmt.Fprint(w, " HAVING ", b.having); err != nil {
return err
}
}
if len(b.orderBy) > 0 {
if _, err := fmt.Fprint(w, " ORDER BY ", b.orderBy); err != nil {
return err
}
}
return nil
}
// OrderBy orderBy SQL
func (b *Builder) OrderBy(orderBy string) *Builder {
b.orderBy = orderBy
return b
}
// GroupBy groupby SQL
func (b *Builder) GroupBy(groupby string) *Builder {
b.groupBy = groupby
return b
}
// Having having SQL
func (b *Builder) Having(having string) *Builder {
b.having = having
return b
}

View File

@ -5,7 +5,6 @@
package builder
import (
"bytes"
"io"
)
@ -19,15 +18,15 @@ var _ Writer = NewWriter()
// BytesWriter implments Writer and save SQL in bytes.Buffer
type BytesWriter struct {
writer *bytes.Buffer
buffer []byte
writer *StringBuilder
args []interface{}
}
// NewWriter creates a new string writer
func NewWriter() *BytesWriter {
w := &BytesWriter{}
w.writer = bytes.NewBuffer(w.buffer)
w := &BytesWriter{
writer: &StringBuilder{},
}
return w
}

View File

@ -10,7 +10,13 @@ import "fmt"
func WriteMap(w Writer, data map[string]interface{}, op string) error {
var args = make([]interface{}, 0, len(data))
var i = 0
for k, v := range data {
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
for _, k := range keys {
v := data[k]
switch v.(type) {
case expr:
if _, err := fmt.Fprintf(w, "%s%s(", k, op); err != nil {

View File

@ -4,7 +4,10 @@
package builder
import "fmt"
import (
"fmt"
"sort"
)
// Incr implements a type used by Eq
type Incr int
@ -19,7 +22,8 @@ var _ Cond = Eq{}
func (eq Eq) opWriteTo(op string, w Writer) error {
var i = 0
for k, v := range eq {
for _, k := range eq.sortedKeys() {
v := eq[k]
switch v.(type) {
case []int, []int64, []string, []int32, []int16, []int8, []uint, []uint64, []uint32, []uint16, []interface{}:
if err := In(k, v).WriteTo(w); err != nil {
@ -94,3 +98,15 @@ func (eq Eq) Or(conds ...Cond) Cond {
func (eq Eq) IsValid() bool {
return len(eq) > 0
}
// sortedKeys returns all keys of this Eq sorted with sort.Strings.
// It is used internally for consistent ordering when generating
// SQL, see https://github.com/go-xorm/builder/issues/10
func (eq Eq) sortedKeys() []string {
keys := make([]string, 0, len(eq))
for key := range eq {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

View File

@ -16,7 +16,7 @@ func (like Like) WriteTo(w Writer) error {
if _, err := fmt.Fprintf(w, "%s LIKE ?", like[0]); err != nil {
return err
}
// FIXME: if use other regular express, this will be failed. but for compitable, keep this
// FIXME: if use other regular express, this will be failed. but for compatible, keep this
if like[1][0] == '%' || like[1][len(like[1])-1] == '%' {
w.Append(like[1])
} else {

View File

@ -4,7 +4,10 @@
package builder
import "fmt"
import (
"fmt"
"sort"
)
// Neq defines not equal conditions
type Neq map[string]interface{}
@ -15,7 +18,8 @@ var _ Cond = Neq{}
func (neq Neq) WriteTo(w Writer) error {
var args = make([]interface{}, 0, len(neq))
var i = 0
for k, v := range neq {
for _, k := range neq.sortedKeys() {
v := neq[k]
switch v.(type) {
case []int, []int64, []string, []int32, []int16, []int8:
if err := NotIn(k, v).WriteTo(w); err != nil {
@ -76,3 +80,15 @@ func (neq Neq) Or(conds ...Cond) Cond {
func (neq Neq) IsValid() bool {
return len(neq) > 0
}
// sortedKeys returns all keys of this Neq sorted with sort.Strings.
// It is used internally for consistent ordering when generating
// SQL, see https://github.com/go-xorm/builder/issues/10
func (neq Neq) sortedKeys() []string {
keys := make([]string, 0, len(neq))
for key := range neq {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

View File

@ -21,6 +21,18 @@ func (not Not) WriteTo(w Writer) error {
if _, err := fmt.Fprint(w, "("); err != nil {
return err
}
case Eq:
if len(not[0].(Eq)) > 1 {
if _, err := fmt.Fprint(w, "("); err != nil {
return err
}
}
case Neq:
if len(not[0].(Neq)) > 1 {
if _, err := fmt.Fprint(w, "("); err != nil {
return err
}
}
}
if err := not[0].WriteTo(w); err != nil {
@ -32,6 +44,18 @@ func (not Not) WriteTo(w Writer) error {
if _, err := fmt.Fprint(w, ")"); err != nil {
return err
}
case Eq:
if len(not[0].(Eq)) > 1 {
if _, err := fmt.Fprint(w, ")"); err != nil {
return err
}
}
case Neq:
if len(not[0].(Neq)) > 1 {
if _, err := fmt.Fprint(w, ")"); err != nil {
return err
}
}
}
return nil

119
vendor/github.com/go-xorm/builder/strings_builder.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
// Copyright 2017 The Go 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 builder
import (
"unicode/utf8"
"unsafe"
)
// A StringBuilder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type StringBuilder struct {
addr *StringBuilder // of receiver, to detect copies by value
buf []byte
}
// noescape hides a pointer from escape analysis. noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input. noescape is inlined and currently
// compiles down to zero instructions.
// USE CAREFULLY!
// This was copied from the runtime; see issues 23382 and 7921.
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
func (b *StringBuilder) copyCheck() {
if b.addr == nil {
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*StringBuilder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
// String returns the accumulated string.
func (b *StringBuilder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
// Len returns the number of accumulated bytes; b.Len() == len(b.String()).
func (b *StringBuilder) Len() int { return len(b.buf) }
// Reset resets the Builder to be empty.
func (b *StringBuilder) Reset() {
b.addr = nil
b.buf = nil
}
// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *StringBuilder) grow(n int) {
buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
copy(buf, b.buf)
b.buf = buf
}
// Grow grows b's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to b
// without another allocation. If n is negative, Grow panics.
func (b *StringBuilder) Grow(n int) {
b.copyCheck()
if n < 0 {
panic("strings.Builder.Grow: negative count")
}
if cap(b.buf)-len(b.buf) < n {
b.grow(n)
}
}
// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *StringBuilder) Write(p []byte) (int, error) {
b.copyCheck()
b.buf = append(b.buf, p...)
return len(p), nil
}
// WriteByte appends the byte c to b's buffer.
// The returned error is always nil.
func (b *StringBuilder) WriteByte(c byte) error {
b.copyCheck()
b.buf = append(b.buf, c)
return nil
}
// WriteRune appends the UTF-8 encoding of Unicode code point r to b's buffer.
// It returns the length of r and a nil error.
func (b *StringBuilder) WriteRune(r rune) (int, error) {
b.copyCheck()
if r < utf8.RuneSelf {
b.buf = append(b.buf, byte(r))
return 1, nil
}
l := len(b.buf)
if cap(b.buf)-l < utf8.UTFMax {
b.grow(utf8.UTFMax)
}
n := utf8.EncodeRune(b.buf[l:l+utf8.UTFMax], r)
b.buf = b.buf[:l+n]
return n, nil
}
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *StringBuilder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}

View File

@ -147,12 +147,12 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) {
}
fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1])
} else {
return nil, fmt.Errorf("field %v is not valid", col.FieldName)
return nil, fmt.Errorf("field %v is not valid", col.FieldName)
}
}
if !fieldValue.IsValid() {
return nil, fmt.Errorf("field %v is not valid", col.FieldName)
return nil, fmt.Errorf("field %v is not valid", col.FieldName)
}
return &fieldValue, nil

57
vendor/github.com/go-xorm/core/db.go generated vendored
View File

@ -7,6 +7,11 @@ import (
"fmt"
"reflect"
"regexp"
"sync"
)
var (
DefaultCacheSize = 200
)
func MapToSlice(query string, mp interface{}) (string, []interface{}, error) {
@ -58,9 +63,16 @@ func StructToSlice(query string, st interface{}) (string, []interface{}, error)
return query, args, nil
}
type cacheStruct struct {
value reflect.Value
idx int
}
type DB struct {
*sql.DB
Mapper IMapper
Mapper IMapper
reflectCache map[reflect.Type]*cacheStruct
reflectCacheMutex sync.RWMutex
}
func Open(driverName, dataSourceName string) (*DB, error) {
@ -68,11 +80,32 @@ func Open(driverName, dataSourceName string) (*DB, error) {
if err != nil {
return nil, err
}
return &DB{db, NewCacheMapper(&SnakeMapper{})}, nil
return &DB{
DB: db,
Mapper: NewCacheMapper(&SnakeMapper{}),
reflectCache: make(map[reflect.Type]*cacheStruct),
}, nil
}
func FromDB(db *sql.DB) *DB {
return &DB{db, NewCacheMapper(&SnakeMapper{})}
return &DB{
DB: db,
Mapper: NewCacheMapper(&SnakeMapper{}),
reflectCache: make(map[reflect.Type]*cacheStruct),
}
}
func (db *DB) reflectNew(typ reflect.Type) reflect.Value {
db.reflectCacheMutex.Lock()
defer db.reflectCacheMutex.Unlock()
cs, ok := db.reflectCache[typ]
if !ok || cs.idx+1 > DefaultCacheSize-1 {
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0}
db.reflectCache[typ] = cs
} else {
cs.idx = cs.idx + 1
}
return cs.value.Index(cs.idx).Addr()
}
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
@ -83,7 +116,7 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
}
return nil, err
}
return &Rows{rows, db.Mapper}, nil
return &Rows{rows, db}, nil
}
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) {
@ -128,8 +161,8 @@ func (db *DB) QueryRowStruct(query string, st interface{}) *Row {
type Stmt struct {
*sql.Stmt
Mapper IMapper
names map[string]int
db *DB
names map[string]int
}
func (db *DB) Prepare(query string) (*Stmt, error) {
@ -145,7 +178,7 @@ func (db *DB) Prepare(query string) (*Stmt, error) {
if err != nil {
return nil, err
}
return &Stmt{stmt, db.Mapper, names}, nil
return &Stmt{stmt, db, names}, nil
}
func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) {
@ -179,7 +212,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
if err != nil {
return nil, err
}
return &Rows{rows, s.Mapper}, nil
return &Rows{rows, s.db}, nil
}
func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) {
@ -274,7 +307,7 @@ func (EmptyScanner) Scan(src interface{}) error {
type Tx struct {
*sql.Tx
Mapper IMapper
db *DB
}
func (db *DB) Begin() (*Tx, error) {
@ -282,7 +315,7 @@ func (db *DB) Begin() (*Tx, error) {
if err != nil {
return nil, err
}
return &Tx{tx, db.Mapper}, nil
return &Tx{tx, db}, nil
}
func (tx *Tx) Prepare(query string) (*Stmt, error) {
@ -298,7 +331,7 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) {
if err != nil {
return nil, err
}
return &Stmt{stmt, tx.Mapper, names}, nil
return &Stmt{stmt, tx.db, names}, nil
}
func (tx *Tx) Stmt(stmt *Stmt) *Stmt {
@ -327,7 +360,7 @@ func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {
if err != nil {
return nil, err
}
return &Rows{rows, tx.Mapper}, nil
return &Rows{rows, tx.db}, nil
}
func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) {

View File

@ -74,6 +74,7 @@ type Dialect interface {
GetIndexes(tableName string) (map[string]*Index, error)
Filters() []Filter
SetParams(params map[string]string)
}
func OpenDialect(dialect Dialect) (*DB, error) {
@ -148,7 +149,8 @@ func (db *Base) SupportDropIfExists() bool {
}
func (db *Base) DropTableSql(tableName string) string {
return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName)
quote := db.dialect.Quote
return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName))
}
func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) {
@ -289,6 +291,9 @@ func (b *Base) LogSQL(sql string, args []interface{}) {
}
}
func (b *Base) SetParams(params map[string]string) {
}
var (
dialects = map[string]func() Dialect{}
)

View File

@ -37,9 +37,9 @@ func (q *Quoter) Quote(content string) string {
func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string {
quoter := NewQuoter(dialect)
if table != nil && len(table.PrimaryKeys) == 1 {
sql = strings.Replace(sql, "`(id)`", quoter.Quote(table.PrimaryKeys[0]), -1)
sql = strings.Replace(sql, quoter.Quote("(id)"), quoter.Quote(table.PrimaryKeys[0]), -1)
return strings.Replace(sql, "(id)", quoter.Quote(table.PrimaryKeys[0]), -1)
sql = strings.Replace(sql, " `(id)` ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
sql = strings.Replace(sql, " "+quoter.Quote("(id)")+" ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
return strings.Replace(sql, " (id) ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
}
return sql
}

View File

@ -22,6 +22,8 @@ type Index struct {
func (index *Index) XName(tableName string) string {
if !strings.HasPrefix(index.Name, "UQE_") &&
!strings.HasPrefix(index.Name, "IDX_") {
tableName = strings.Replace(tableName, `"`, "", -1)
tableName = strings.Replace(tableName, `.`, "_", -1)
if index.Type == UniqueType {
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
}

View File

@ -9,7 +9,7 @@ import (
type Rows struct {
*sql.Rows
Mapper IMapper
db *DB
}
func (rs *Rows) ToMapString() ([]map[string]string, error) {
@ -105,7 +105,7 @@ func (rs *Rows) ScanStructByName(dest interface{}) error {
newDest := make([]interface{}, len(cols))
var v EmptyScanner
for j, name := range cols {
f := fieldByName(vv.Elem(), rs.Mapper.Table2Obj(name))
f := fieldByName(vv.Elem(), rs.db.Mapper.Table2Obj(name))
if f.IsValid() {
newDest[j] = f.Addr().Interface()
} else {
@ -116,36 +116,6 @@ func (rs *Rows) ScanStructByName(dest interface{}) error {
return rs.Rows.Scan(newDest...)
}
type cacheStruct struct {
value reflect.Value
idx int
}
var (
reflectCache = make(map[reflect.Type]*cacheStruct)
reflectCacheMutex sync.RWMutex
)
func ReflectNew(typ reflect.Type) reflect.Value {
reflectCacheMutex.RLock()
cs, ok := reflectCache[typ]
reflectCacheMutex.RUnlock()
const newSize = 200
if !ok || cs.idx+1 > newSize-1 {
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), newSize, newSize), 0}
reflectCacheMutex.Lock()
reflectCache[typ] = cs
reflectCacheMutex.Unlock()
} else {
reflectCacheMutex.Lock()
cs.idx = cs.idx + 1
reflectCacheMutex.Unlock()
}
return cs.value.Index(cs.idx).Addr()
}
// scan data to a slice's pointer, slice's length should equal to columns' number
func (rs *Rows) ScanSlice(dest interface{}) error {
vv := reflect.ValueOf(dest)
@ -197,9 +167,7 @@ func (rs *Rows) ScanMap(dest interface{}) error {
vvv := vv.Elem()
for i, _ := range cols {
newDest[i] = ReflectNew(vvv.Type().Elem()).Interface()
//v := reflect.New(vvv.Type().Elem())
//newDest[i] = v.Interface()
newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface()
}
err = rs.Rows.Scan(newDest...)
@ -215,32 +183,6 @@ func (rs *Rows) ScanMap(dest interface{}) error {
return nil
}
/*func (rs *Rows) ScanMap(dest interface{}) error {
vv := reflect.ValueOf(dest)
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
return errors.New("dest should be a map's pointer")
}
cols, err := rs.Columns()
if err != nil {
return err
}
newDest := make([]interface{}, len(cols))
err = rs.ScanSlice(newDest)
if err != nil {
return err
}
vvv := vv.Elem()
for i, name := range cols {
vname := reflect.ValueOf(name)
vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem())
}
return nil
}*/
type Row struct {
rows *Rows
// One of these two will be non-nil:

View File

@ -49,7 +49,6 @@ func NewTable(name string, t reflect.Type) *Table {
}
func (table *Table) columnsByName(name string) []*Column {
n := len(name)
for k := range table.columnsMap {
@ -75,7 +74,6 @@ func (table *Table) GetColumn(name string) *Column {
}
func (table *Table) GetColumnIdx(name string, idx int) *Column {
cols := table.columnsByName(name)
if cols != nil && idx < len(cols) {

View File

@ -69,15 +69,18 @@ var (
Enum = "ENUM"
Set = "SET"
Char = "CHAR"
Varchar = "VARCHAR"
NVarchar = "NVARCHAR"
TinyText = "TINYTEXT"
Text = "TEXT"
Clob = "CLOB"
MediumText = "MEDIUMTEXT"
LongText = "LONGTEXT"
Uuid = "UUID"
Char = "CHAR"
Varchar = "VARCHAR"
NVarchar = "NVARCHAR"
TinyText = "TINYTEXT"
Text = "TEXT"
NText = "NTEXT"
Clob = "CLOB"
MediumText = "MEDIUMTEXT"
LongText = "LONGTEXT"
Uuid = "UUID"
UniqueIdentifier = "UNIQUEIDENTIFIER"
SysName = "SYSNAME"
Date = "DATE"
DateTime = "DATETIME"
@ -128,10 +131,12 @@ var (
NVarchar: TEXT_TYPE,
TinyText: TEXT_TYPE,
Text: TEXT_TYPE,
NText: TEXT_TYPE,
MediumText: TEXT_TYPE,
LongText: TEXT_TYPE,
Uuid: TEXT_TYPE,
Clob: TEXT_TYPE,
SysName: TEXT_TYPE,
Date: TIME_TYPE,
DateTime: TIME_TYPE,
@ -148,11 +153,12 @@ var (
Binary: BLOB_TYPE,
VarBinary: BLOB_TYPE,
TinyBlob: BLOB_TYPE,
Blob: BLOB_TYPE,
MediumBlob: BLOB_TYPE,
LongBlob: BLOB_TYPE,
Bytea: BLOB_TYPE,
TinyBlob: BLOB_TYPE,
Blob: BLOB_TYPE,
MediumBlob: BLOB_TYPE,
LongBlob: BLOB_TYPE,
Bytea: BLOB_TYPE,
UniqueIdentifier: BLOB_TYPE,
Bool: NUMERIC_TYPE,
@ -289,9 +295,9 @@ func SQLType2Type(st SQLType) reflect.Type {
return reflect.TypeOf(float32(1))
case Double:
return reflect.TypeOf(float64(1))
case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob:
case Char, Varchar, NVarchar, TinyText, Text, NText, MediumText, LongText, Enum, Set, Uuid, Clob, SysName:
return reflect.TypeOf("")
case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary:
case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier:
return reflect.TypeOf([]byte{})
case Bool:
return reflect.TypeOf(true)

View File

@ -172,12 +172,33 @@ type mysql struct {
allowAllFiles bool
allowOldPasswords bool
clientFoundRows bool
rowFormat string
}
func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
return db.Base.Init(d, db, uri, drivername, dataSourceName)
}
func (db *mysql) SetParams(params map[string]string) {
rowFormat, ok := params["rowFormat"]
if ok {
var t = strings.ToUpper(rowFormat)
switch t {
case "COMPACT":
fallthrough
case "REDUNDANT":
fallthrough
case "DYNAMIC":
fallthrough
case "COMPRESSED":
db.rowFormat = t
break
default:
break
}
}
}
func (db *mysql) SqlType(c *core.Column) string {
var res string
switch t := c.SQLType.Name; t {
@ -487,6 +508,59 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) {
return indexes, nil
}
func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string {
var sql string
sql = "CREATE TABLE IF NOT EXISTS "
if tableName == "" {
tableName = table.Name
}
sql += db.Quote(tableName)
sql += " ("
if len(table.ColumnsSeq()) > 0 {
pkList := table.PrimaryKeys
for _, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
if col.IsPrimaryKey && len(pkList) == 1 {
sql += col.String(db)
} else {
sql += col.StringNoPk(db)
}
sql = strings.TrimSpace(sql)
if len(col.Comment) > 0 {
sql += " COMMENT '" + col.Comment + "'"
}
sql += ", "
}
if len(pkList) > 1 {
sql += "PRIMARY KEY ( "
sql += db.Quote(strings.Join(pkList, db.Quote(",")))
sql += " ), "
}
sql = sql[:len(sql)-2]
}
sql += ")"
if storeEngine != "" {
sql += " ENGINE=" + storeEngine
}
if len(charset) == 0 {
charset = db.URI().Charset
} else if len(charset) > 0 {
sql += " DEFAULT CHARSET " + charset
}
if db.rowFormat != "" {
sql += " ROW_FORMAT=" + db.rowFormat
}
return sql
}
func (db *mysql) Filters() []core.Filter {
return []core.Filter{&core.IdFilter{}}
}

View File

@ -769,14 +769,21 @@ var (
DefaultPostgresSchema = "public"
)
const postgresPublicSchema = "public"
type postgres struct {
core.Base
schema string
}
func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
db.schema = DefaultPostgresSchema
return db.Base.Init(d, db, uri, drivername, dataSourceName)
err := db.Base.Init(d, db, uri, drivername, dataSourceName)
if err != nil {
return err
}
if db.Schema == "" {
db.Schema = DefaultPostgresSchema
}
return nil
}
func (db *postgres) SqlType(c *core.Column) string {
@ -873,32 +880,42 @@ func (db *postgres) IndexOnTable() bool {
}
func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
args := []interface{}{tableName, idxName}
if len(db.Schema) == 0 {
args := []interface{}{tableName, idxName}
return `SELECT indexname FROM pg_indexes WHERE tablename = ? AND indexname = ?`, args
}
args := []interface{}{db.Schema, tableName, idxName}
return `SELECT indexname FROM pg_indexes ` +
`WHERE tablename = ? AND indexname = ?`, args
`WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args
}
func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) {
args := []interface{}{tableName}
return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args
if len(db.Schema) == 0 {
args := []interface{}{tableName}
return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args
}
args := []interface{}{db.Schema, tableName}
return `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, args
}
/*func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
args := []interface{}{tableName, colName}
return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" +
" AND column_name = ?", args
}*/
func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string {
return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s",
tableName, col.Name, db.SqlType(col))
if len(db.Schema) == 0 {
return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s",
tableName, col.Name, db.SqlType(col))
}
return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s",
db.Schema, tableName, col.Name, db.SqlType(col))
}
func (db *postgres) DropIndexSql(tableName string, index *core.Index) string {
//var unique string
quote := db.Quote
idxName := index.Name
tableName = strings.Replace(tableName, `"`, "", -1)
tableName = strings.Replace(tableName, `.`, "_", -1)
if !strings.HasPrefix(idxName, "UQE_") &&
!strings.HasPrefix(idxName, "IDX_") {
if index.Type == core.UniqueType {
@ -907,13 +924,21 @@ func (db *postgres) DropIndexSql(tableName string, index *core.Index) string {
idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
}
}
if db.Uri.Schema != "" {
idxName = db.Uri.Schema + "." + idxName
}
return fmt.Sprintf("DROP INDEX %v", quote(idxName))
}
func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) {
args := []interface{}{tableName, colName}
query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" +
" AND column_name = $2"
args := []interface{}{db.Schema, tableName, colName}
query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_name = $2" +
" AND column_name = $3"
if len(db.Schema) == 0 {
args = []interface{}{tableName, colName}
query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" +
" AND column_name = $2"
}
db.LogSQL(query, args)
rows, err := db.DB().Query(query, args...)
@ -926,8 +951,7 @@ func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) {
}
func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
// FIXME: the schema should be replaced by user custom's
args := []interface{}{tableName, db.schema}
args := []interface{}{tableName}
s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix ,
CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey,
CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey
@ -938,7 +962,15 @@ FROM pg_attribute f
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name
WHERE c.relkind = 'r'::char AND c.relname = $1 AND s.table_schema = $2 AND f.attnum > 0 ORDER BY f.attnum;`
WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;`
var f string
if len(db.Schema) != 0 {
args = append(args, db.Schema)
f = " AND s.table_schema = $2"
}
s = fmt.Sprintf(s, f)
db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...)
@ -1028,8 +1060,13 @@ WHERE c.relkind = 'r'::char AND c.relname = $1 AND s.table_schema = $2 AND f.att
}
func (db *postgres) GetTables() ([]*core.Table, error) {
args := []interface{}{db.schema}
s := fmt.Sprintf("SELECT tablename FROM pg_tables WHERE schemaname = $1")
args := []interface{}{}
s := "SELECT tablename FROM pg_tables"
if len(db.Schema) != 0 {
args = append(args, db.Schema)
s = s + " WHERE schemaname = $1"
}
db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...)
@ -1053,8 +1090,12 @@ func (db *postgres) GetTables() ([]*core.Table, error) {
}
func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) {
args := []interface{}{db.schema, tableName}
s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE schemaname=$1 AND tablename=$2")
args := []interface{}{tableName}
s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1")
if len(db.Schema) != 0 {
args = append(args, db.Schema)
s = s + " AND schemaname=$2"
}
db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...)
@ -1182,3 +1223,15 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
return db, nil
}
type pqDriverPgx struct {
pqDriver
}
func (pgx *pqDriverPgx) Parse(driverName, dataSourceName string) (*core.Uri, error) {
// Remove the leading characters for driver to work
if len(dataSourceName) >= 9 && dataSourceName[0] == 0 {
dataSourceName = dataSourceName[9:]
}
return pgx.pqDriver.Parse(driverName, dataSourceName)
}

View File

@ -49,6 +49,35 @@ type Engine struct {
tagHandlers map[string]tagHandler
engineGroup *EngineGroup
cachers map[string]core.Cacher
cacherLock sync.RWMutex
}
func (engine *Engine) setCacher(tableName string, cacher core.Cacher) {
engine.cacherLock.Lock()
engine.cachers[tableName] = cacher
engine.cacherLock.Unlock()
}
func (engine *Engine) SetCacher(tableName string, cacher core.Cacher) {
engine.setCacher(tableName, cacher)
}
func (engine *Engine) getCacher(tableName string) core.Cacher {
var cacher core.Cacher
var ok bool
engine.cacherLock.RLock()
cacher, ok = engine.cachers[tableName]
engine.cacherLock.RUnlock()
if !ok && !engine.disableGlobalCache {
cacher = engine.Cacher
}
return cacher
}
func (engine *Engine) GetCacher(tableName string) core.Cacher {
return engine.getCacher(tableName)
}
// BufferSize sets buffer size for iterate
@ -165,7 +194,7 @@ func (engine *Engine) Quote(value string) string {
}
// QuoteTo quotes string and writes into the buffer
func (engine *Engine) QuoteTo(buf *bytes.Buffer, value string) {
func (engine *Engine) QuoteTo(buf *builder.StringBuilder, value string) {
if buf == nil {
return
}
@ -245,13 +274,7 @@ func (engine *Engine) NoCascade() *Session {
// MapCacher Set a table use a special cacher
func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error {
v := rValue(bean)
tb, err := engine.autoMapType(v)
if err != nil {
return err
}
tb.Cacher = cacher
engine.setCacher(engine.TableName(bean, true), cacher)
return nil
}
@ -536,33 +559,6 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D
return nil
}
func (engine *Engine) tableName(beanOrTableName interface{}) (string, error) {
v := rValue(beanOrTableName)
if v.Type().Kind() == reflect.String {
return beanOrTableName.(string), nil
} else if v.Type().Kind() == reflect.Struct {
return engine.tbName(v), nil
}
return "", errors.New("bean should be a struct or struct's point")
}
func (engine *Engine) tbName(v reflect.Value) string {
if tb, ok := v.Interface().(TableName); ok {
return tb.TableName()
}
if v.Type().Kind() == reflect.Ptr {
if tb, ok := reflect.Indirect(v).Interface().(TableName); ok {
return tb.TableName()
}
} else if v.CanAddr() {
if tb, ok := v.Addr().Interface().(TableName); ok {
return tb.TableName()
}
}
return engine.TableMapper.Obj2Table(reflect.Indirect(v).Type().Name())
}
// Cascade use cascade or not
func (engine *Engine) Cascade(trueOrFalse ...bool) *Session {
session := engine.NewSession()
@ -846,7 +842,7 @@ func (engine *Engine) TableInfo(bean interface{}) *Table {
if err != nil {
engine.logger.Error(err)
}
return &Table{tb, engine.tbName(v)}
return &Table{tb, engine.TableName(bean)}
}
func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) {
@ -861,15 +857,6 @@ func addIndex(indexName string, table *core.Table, col *core.Column, indexType i
}
}
func (engine *Engine) newTable() *core.Table {
table := core.NewEmptyTable()
if !engine.disableGlobalCache {
table.Cacher = engine.Cacher
}
return table
}
// TableName table name interface to define customerize table name
type TableName interface {
TableName() string
@ -881,21 +868,9 @@ var (
func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
t := v.Type()
table := engine.newTable()
if tb, ok := v.Interface().(TableName); ok {
table.Name = tb.TableName()
} else {
if v.CanAddr() {
if tb, ok = v.Addr().Interface().(TableName); ok {
table.Name = tb.TableName()
}
}
if table.Name == "" {
table.Name = engine.TableMapper.Obj2Table(t.Name())
}
}
table := core.NewEmptyTable()
table.Type = t
table.Name = engine.tbNameForMap(v)
var idFieldColName string
var hasCacheTag, hasNoCacheTag bool
@ -1049,15 +1024,15 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
if hasCacheTag {
if engine.Cacher != nil { // !nash! use engine's cacher if provided
engine.logger.Info("enable cache on table:", table.Name)
table.Cacher = engine.Cacher
engine.setCacher(table.Name, engine.Cacher)
} else {
engine.logger.Info("enable LRU cache on table:", table.Name)
table.Cacher = NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) // !nashtsai! HACK use LRU cacher for now
engine.setCacher(table.Name, NewLRUCacher2(NewMemoryStore(), time.Hour, 10000))
}
}
if hasNoCacheTag {
engine.logger.Info("no cache on table:", table.Name)
table.Cacher = nil
engine.logger.Info("disable cache on table:", table.Name)
engine.setCacher(table.Name, nil)
}
return table, nil
@ -1116,7 +1091,25 @@ func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) {
pk := make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() {
var err error
pkField := v.FieldByName(col.FieldName)
fieldName := col.FieldName
for {
parts := strings.SplitN(fieldName, ".", 2)
if len(parts) == 1 {
break
}
v = v.FieldByName(parts[0])
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, ErrUnSupportedType
}
fieldName = parts[1]
}
pkField := v.FieldByName(fieldName)
switch pkField.Kind() {
case reflect.String:
pk[i], err = engine.idTypeAssertion(col, pkField.String())
@ -1162,26 +1155,10 @@ func (engine *Engine) CreateUniques(bean interface{}) error {
return session.CreateUniques(bean)
}
func (engine *Engine) getCacher2(table *core.Table) core.Cacher {
return table.Cacher
}
// ClearCacheBean if enabled cache, clear the cache bean
func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
v := rValue(bean)
t := v.Type()
if t.Kind() != reflect.Struct {
return errors.New("error params")
}
tableName := engine.tbName(v)
table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher
if cacher == nil {
cacher = engine.Cacher
}
tableName := engine.TableName(bean)
cacher := engine.getCacher(tableName)
if cacher != nil {
cacher.ClearIds(tableName)
cacher.DelBean(tableName, id)
@ -1192,21 +1169,8 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
// ClearCache if enabled cache, clear some tables' cache
func (engine *Engine) ClearCache(beans ...interface{}) error {
for _, bean := range beans {
v := rValue(bean)
t := v.Type()
if t.Kind() != reflect.Struct {
return errors.New("error params")
}
tableName := engine.tbName(v)
table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher
if cacher == nil {
cacher = engine.Cacher
}
tableName := engine.TableName(bean)
cacher := engine.getCacher(tableName)
if cacher != nil {
cacher.ClearIds(tableName)
cacher.ClearBeans(tableName)
@ -1224,13 +1188,13 @@ func (engine *Engine) Sync(beans ...interface{}) error {
for _, bean := range beans {
v := rValue(bean)
tableName := engine.tbName(v)
tableNameNoSchema := engine.TableName(bean)
table, err := engine.autoMapType(v)
if err != nil {
return err
}
isExist, err := session.Table(bean).isTableExist(tableName)
isExist, err := session.Table(bean).isTableExist(tableNameNoSchema)
if err != nil {
return err
}
@ -1256,12 +1220,12 @@ func (engine *Engine) Sync(beans ...interface{}) error {
}
} else {
for _, col := range table.Columns() {
isExist, err := engine.dialect.IsColumnExist(tableName, col.Name)
isExist, err := engine.dialect.IsColumnExist(tableNameNoSchema, col.Name)
if err != nil {
return err
}
if !isExist {
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
err = session.addColumn(col.Name)
@ -1272,35 +1236,35 @@ func (engine *Engine) Sync(beans ...interface{}) error {
}
for name, index := range table.Indexes {
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
if index.Type == core.UniqueType {
isExist, err := session.isIndexExist2(tableName, index.Cols, true)
isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true)
if err != nil {
return err
}
if !isExist {
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
err = session.addUnique(tableName, name)
err = session.addUnique(tableNameNoSchema, name)
if err != nil {
return err
}
}
} else if index.Type == core.IndexType {
isExist, err := session.isIndexExist2(tableName, index.Cols, false)
isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, false)
if err != nil {
return err
}
if !isExist {
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
err = session.addIndex(tableName, name)
err = session.addIndex(tableNameNoSchema, name)
if err != nil {
return err
}
@ -1453,6 +1417,13 @@ func (engine *Engine) Find(beans interface{}, condiBeans ...interface{}) error {
return session.Find(beans, condiBeans...)
}
// FindAndCount find the results and also return the counts
func (engine *Engine) FindAndCount(rowsSlicePtr interface{}, condiBean ...interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
return session.FindAndCount(rowsSlicePtr, condiBean...)
}
// Iterate record by record handle records from table, bean's non-empty fields
// are conditions.
func (engine *Engine) Iterate(bean interface{}, fun IterFunc) error {
@ -1629,6 +1600,11 @@ func (engine *Engine) SetTZDatabase(tz *time.Location) {
engine.DatabaseTZ = tz
}
// SetSchema sets the schema of database
func (engine *Engine) SetSchema(schema string) {
engine.dialect.URI().Schema = schema
}
// Unscoped always disable struct tag "deleted"
func (engine *Engine) Unscoped() *Session {
session := engine.NewSession()

View File

@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/go-xorm/builder"
@ -51,7 +52,9 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{},
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
engine.logger.Error(err)
if !strings.Contains(err.Error(), "is not valid") {
engine.logger.Warn(err)
}
continue
}

113
vendor/github.com/go-xorm/xorm/engine_table.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
// Copyright 2018 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 xorm
import (
"fmt"
"reflect"
"strings"
"github.com/go-xorm/core"
)
// TableNameWithSchema will automatically add schema prefix on table name
func (engine *Engine) tbNameWithSchema(v string) string {
// Add schema name as prefix of table name.
// Only for postgres database.
if engine.dialect.DBType() == core.POSTGRES &&
engine.dialect.URI().Schema != "" &&
engine.dialect.URI().Schema != postgresPublicSchema &&
strings.Index(v, ".") == -1 {
return engine.dialect.URI().Schema + "." + v
}
return v
}
// TableName returns table name with schema prefix if has
func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string {
tbName := engine.tbNameNoSchema(bean)
if len(includeSchema) > 0 && includeSchema[0] {
tbName = engine.tbNameWithSchema(tbName)
}
return tbName
}
// tbName get some table's table name
func (session *Session) tbNameNoSchema(table *core.Table) string {
if len(session.statement.AltTableName) > 0 {
return session.statement.AltTableName
}
return table.Name
}
func (engine *Engine) tbNameForMap(v reflect.Value) string {
if v.Type().Implements(tpTableName) {
return v.Interface().(TableName).TableName()
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
if v.Type().Implements(tpTableName) {
return v.Interface().(TableName).TableName()
}
}
return engine.TableMapper.Obj2Table(v.Type().Name())
}
func (engine *Engine) tbNameNoSchema(tablename interface{}) string {
switch tablename.(type) {
case []string:
t := tablename.([]string)
if len(t) > 1 {
return fmt.Sprintf("%v AS %v", engine.Quote(t[0]), engine.Quote(t[1]))
} else if len(t) == 1 {
return engine.Quote(t[0])
}
case []interface{}:
t := tablename.([]interface{})
l := len(t)
var table string
if l > 0 {
f := t[0]
switch f.(type) {
case string:
table = f.(string)
case TableName:
table = f.(TableName).TableName()
default:
v := rValue(f)
t := v.Type()
if t.Kind() == reflect.Struct {
table = engine.tbNameForMap(v)
} else {
table = engine.Quote(fmt.Sprintf("%v", f))
}
}
}
if l > 1 {
return fmt.Sprintf("%v AS %v", engine.Quote(table),
engine.Quote(fmt.Sprintf("%v", t[1])))
} else if l == 1 {
return engine.Quote(table)
}
case TableName:
return tablename.(TableName).TableName()
case string:
return tablename.(string)
case reflect.Value:
v := tablename.(reflect.Value)
return engine.tbNameForMap(v)
default:
v := rValue(tablename)
t := v.Type()
if t.Kind() == reflect.Struct {
return engine.tbNameForMap(v)
}
return engine.Quote(fmt.Sprintf("%v", tablename))
}
return ""
}

View File

@ -6,23 +6,44 @@ package xorm
import (
"errors"
"fmt"
)
var (
// ErrParamsType params error
ErrParamsType = errors.New("Params type error")
// ErrTableNotFound table not found error
ErrTableNotFound = errors.New("Not found table")
ErrTableNotFound = errors.New("Table not found")
// ErrUnSupportedType unsupported error
ErrUnSupportedType = errors.New("Unsupported type error")
// ErrNotExist record is not exist error
ErrNotExist = errors.New("Not exist error")
// ErrNotExist record does not exist error
ErrNotExist = errors.New("Record does not exist")
// ErrCacheFailed cache failed error
ErrCacheFailed = errors.New("Cache failed")
// ErrNeedDeletedCond delete needs less one condition error
ErrNeedDeletedCond = errors.New("Delete need at least one condition")
ErrNeedDeletedCond = errors.New("Delete action needs at least one condition")
// ErrNotImplemented not implemented
ErrNotImplemented = errors.New("Not implemented")
// ErrConditionType condition type unsupported
ErrConditionType = errors.New("Unsupported conditon type")
ErrConditionType = errors.New("Unsupported condition type")
)
// ErrFieldIsNotExist columns does not exist
type ErrFieldIsNotExist struct {
FieldName string
TableName string
}
func (e ErrFieldIsNotExist) Error() string {
return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName)
}
// ErrFieldIsNotValid is not valid
type ErrFieldIsNotValid struct {
FieldName string
TableName string
}
func (e ErrFieldIsNotValid) Error() string {
return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName)
}

View File

@ -11,7 +11,6 @@ import (
"sort"
"strconv"
"strings"
"time"
"github.com/go-xorm/core"
)
@ -293,19 +292,6 @@ func structName(v reflect.Type) string {
return v.Name()
}
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
}
func sliceEq(left, right []string) bool {
if len(left) != len(right) {
return false
@ -320,154 +306,6 @@ func sliceEq(left, right []string) bool {
return true
}
func setColumnInt(bean interface{}, col *core.Column, t int64) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t)
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t))
}
}
}
func setColumnTime(bean interface{}, col *core.Column, t time.Time) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Struct:
v.Set(reflect.ValueOf(t).Convert(v.Type()))
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t.Unix())
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t.Unix()))
}
}
}
func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) {
colNames := make([]string, 0, len(table.ColumnsSeq()))
args := make([]interface{}, 0, len(table.ColumnsSeq()))
for _, col := range table.Columns() {
if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
}
}
if col.MapType == core.ONLYFROMDB {
continue
}
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return nil, nil, err
}
fieldValue := *fieldValuePtr
if col.IsAutoIncrement {
switch fieldValue.Type().Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
if fieldValue.Int() == 0 {
continue
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
if fieldValue.Uint() == 0 {
continue
}
case reflect.String:
if len(fieldValue.String()) == 0 {
continue
}
case reflect.Ptr:
if fieldValue.Pointer() == 0 {
continue
}
}
}
if col.IsDeleted {
continue
}
if session.statement.ColumnStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
} else if _, ok := session.statement.incrColumns[col.Name]; ok {
continue
} else if _, ok := session.statement.decrColumns[col.Name]; ok {
continue
}
}
if session.statement.OmitStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue
}
}
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int
fieldValue = reflect.ValueOf(nilValue)
}
}
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
} else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
} else {
arg, err := session.value2Interface(col, fieldValue)
if err != nil {
return colNames, args, err
}
args = append(args, arg)
}
if includeQuote {
colNames = append(colNames, session.engine.Quote(col.Name)+" = ?")
} else {
colNames = append(colNames, col.Name)
}
}
return colNames, args, nil
}
func indexName(tableName, idxName string) string {
return fmt.Sprintf("IDX_%v_%v", tableName, idxName)
}
func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) {
if len(m) == 0 {
return false, false
}
n := len(col.Name)
for mk := range m {
if len(mk) != n {
continue
}
if strings.EqualFold(mk, col.Name) {
return m[mk], true
}
}
return false, false
}

View File

@ -30,6 +30,7 @@ type Interface interface {
Exec(string, ...interface{}) (sql.Result, error)
Exist(bean ...interface{}) (bool, error)
Find(interface{}, ...interface{}) error
FindAndCount(interface{}, ...interface{}) (int64, error)
Get(interface{}) (bool, error)
GroupBy(keys string) *Session
ID(interface{}) *Session
@ -41,6 +42,7 @@ type Interface interface {
IsTableExist(beanOrTableName interface{}) (bool, error)
Iterate(interface{}, IterFunc) error
Limit(int, ...int) *Session
MustCols(columns ...string) *Session
NoAutoCondition(...bool) *Session
NotIn(string, ...interface{}) *Session
Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session
@ -75,6 +77,7 @@ type EngineInterface interface {
Dialect() core.Dialect
DropTables(...interface{}) error
DumpAllToFile(fp string, tp ...core.DbType) error
GetCacher(string) core.Cacher
GetColumnMapper() core.IMapper
GetDefaultCacher() core.Cacher
GetTableMapper() core.IMapper
@ -83,9 +86,11 @@ type EngineInterface interface {
NewSession() *Session
NoAutoTime() *Session
Quote(string) string
SetCacher(string, core.Cacher)
SetDefaultCacher(core.Cacher)
SetLogLevel(core.LogLevel)
SetMapper(core.IMapper)
SetSchema(string)
SetTZDatabase(tz *time.Location)
SetTZLocation(tz *time.Location)
ShowSQL(show ...bool)
@ -93,6 +98,7 @@ type EngineInterface interface {
Sync2(...interface{}) error
StoreEngine(storeEngine string) *Session
TableInfo(bean interface{}) *Table
TableName(interface{}, ...bool) string
UnMapType(reflect.Type)
}

View File

@ -32,7 +32,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
var args []interface{}
var err error
if err = rows.session.statement.setRefValue(rValue(bean)); err != nil {
if err = rows.session.statement.setRefBean(bean); err != nil {
return nil, err
}
@ -94,8 +94,7 @@ func (rows *Rows) Scan(bean interface{}) error {
return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType)
}
dataStruct := rValue(bean)
if err := rows.session.statement.setRefValue(dataStruct); err != nil {
if err := rows.session.statement.setRefBean(bean); err != nil {
return err
}
@ -104,6 +103,7 @@ func (rows *Rows) Scan(bean interface{}) error {
return err
}
dataStruct := rValue(bean)
_, err = rows.session.slice2Bean(scanResults, rows.fields, bean, &dataStruct, rows.session.statement.RefTable)
if err != nil {
return err

View File

@ -278,24 +278,22 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt,
return
}
func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) *reflect.Value {
func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) (*reflect.Value, error) {
var col *core.Column
if col = table.GetColumnIdx(key, idx); col == nil {
//session.engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq())
return nil
return nil, ErrFieldIsNotExist{key, table.Name}
}
fieldValue, err := col.ValueOfV(dataStruct)
if err != nil {
session.engine.logger.Error(err)
return nil
return nil, err
}
if !fieldValue.IsValid() || !fieldValue.CanSet() {
session.engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key)
return nil
return nil, ErrFieldIsNotValid{key, table.Name}
}
return fieldValue
return fieldValue, nil
}
// Cell cell is a result of one column field
@ -407,409 +405,417 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
}
tempMap[lKey] = idx
if fieldValue := session.getField(dataStruct, key, table, idx); fieldValue != nil {
rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii]))
// if row is null then ignore
if rawValue.Interface() == nil {
continue
fieldValue, err := session.getField(dataStruct, key, table, idx)
if err != nil {
if !strings.Contains(err.Error(), "is not valid") {
session.engine.logger.Warn(err)
}
continue
}
if fieldValue == nil {
continue
}
rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii]))
if fieldValue.CanAddr() {
if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil {
if err := structConvert.FromDB(data); err != nil {
return nil, err
}
} else {
// if row is null then ignore
if rawValue.Interface() == nil {
continue
}
if fieldValue.CanAddr() {
if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil {
if err := structConvert.FromDB(data); err != nil {
return nil, err
}
continue
}
}
if _, ok := fieldValue.Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil {
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
}
fieldValue.Interface().(core.Conversion).FromDB(data)
} else {
return nil, err
}
continue
}
}
rawValueType := reflect.TypeOf(rawValue.Interface())
vv := reflect.ValueOf(rawValue.Interface())
col := table.GetColumnIdx(key, idx)
if col.IsPrimaryKey {
pk = append(pk, rawValue.Interface())
if _, ok := fieldValue.Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil {
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
}
fieldValue.Interface().(core.Conversion).FromDB(data)
} else {
return nil, err
}
fieldType := fieldValue.Type()
hasAssigned := false
continue
}
if col.SQLType.IsJson() {
var bs []byte
if rawValueType.Kind() == reflect.String {
bs = []byte(vv.String())
} else if rawValueType.ConvertibleTo(core.BytesType) {
bs = vv.Bytes()
rawValueType := reflect.TypeOf(rawValue.Interface())
vv := reflect.ValueOf(rawValue.Interface())
col := table.GetColumnIdx(key, idx)
if col.IsPrimaryKey {
pk = append(pk, rawValue.Interface())
}
fieldType := fieldValue.Type()
hasAssigned := false
if col.SQLType.IsJson() {
var bs []byte
if rawValueType.Kind() == reflect.String {
bs = []byte(vv.String())
} else if rawValueType.ConvertibleTo(core.BytesType) {
bs = vv.Bytes()
} else {
return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind())
}
hasAssigned = true
if len(bs) > 0 {
if fieldType.Kind() == reflect.String {
fieldValue.SetString(string(bs))
continue
}
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind())
}
hasAssigned = true
if len(bs) > 0 {
if fieldType.Kind() == reflect.String {
fieldValue.SetString(string(bs))
continue
}
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
return nil, err
}
} else {
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
continue
}
switch fieldType.Kind() {
case reflect.Complex64, reflect.Complex128:
// TODO: reimplement this
var bs []byte
if rawValueType.Kind() == reflect.String {
bs = []byte(vv.String())
} else if rawValueType.ConvertibleTo(core.BytesType) {
bs = vv.Bytes()
}
continue
}
hasAssigned = true
if len(bs) > 0 {
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
return nil, err
}
} else {
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
switch fieldType.Kind() {
case reflect.Complex64, reflect.Complex128:
// TODO: reimplement this
var bs []byte
if rawValueType.Kind() == reflect.String {
bs = []byte(vv.String())
} else if rawValueType.ConvertibleTo(core.BytesType) {
bs = vv.Bytes()
}
hasAssigned = true
if len(bs) > 0 {
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
return nil, err
}
} else {
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
}
case reflect.Slice, reflect.Array:
switch rawValueType.Kind() {
case reflect.Slice, reflect.Array:
switch rawValueType.Kind() {
case reflect.Slice, reflect.Array:
switch rawValueType.Elem().Kind() {
case reflect.Uint8:
if fieldType.Elem().Kind() == reflect.Uint8 {
hasAssigned = true
if col.SQLType.IsText() {
x := reflect.New(fieldType)
err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
} else {
if fieldValue.Len() > 0 {
for i := 0; i < fieldValue.Len(); i++ {
if i < vv.Len() {
fieldValue.Index(i).Set(vv.Index(i))
}
}
} else {
for i := 0; i < vv.Len(); i++ {
fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i)))
}
}
}
}
}
}
case reflect.String:
if rawValueType.Kind() == reflect.String {
hasAssigned = true
fieldValue.SetString(vv.String())
}
case reflect.Bool:
if rawValueType.Kind() == reflect.Bool {
hasAssigned = true
fieldValue.SetBool(vv.Bool())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch rawValueType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
hasAssigned = true
fieldValue.SetInt(vv.Int())
}
case reflect.Float32, reflect.Float64:
switch rawValueType.Kind() {
case reflect.Float32, reflect.Float64:
hasAssigned = true
fieldValue.SetFloat(vv.Float())
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
switch rawValueType.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
hasAssigned = true
fieldValue.SetUint(vv.Uint())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
hasAssigned = true
fieldValue.SetUint(uint64(vv.Int()))
}
case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) {
dbTZ := session.engine.DatabaseTZ
if col.TimeZone != nil {
dbTZ = col.TimeZone
}
if rawValueType == core.TimeType {
switch rawValueType.Elem().Kind() {
case reflect.Uint8:
if fieldType.Elem().Kind() == reflect.Uint8 {
hasAssigned = true
t := vv.Convert(core.TimeType).Interface().(time.Time)
z, _ := t.Zone()
// set new location if database don't save timezone or give an incorrect timezone
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location
session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
t.Minute(), t.Second(), t.Nanosecond(), dbTZ)
}
t = t.In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else if rawValueType == core.IntType || rawValueType == core.Int64Type ||
rawValueType == core.Int32Type {
hasAssigned = true
t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else {
if d, ok := vv.Interface().([]uint8); ok {
hasAssigned = true
t, err := session.byte2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else if d, ok := vv.Interface().(string); ok {
hasAssigned = true
t, err := session.str2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else {
return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface())
}
}
} else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
// !<winxxp>! 增加支持sql.Scanner接口的结构如sql.NullString
hasAssigned = true
if err := nulVal.Scan(vv.Interface()); err != nil {
session.engine.logger.Error("sql.Sanner error:", err.Error())
hasAssigned = false
}
} else if col.SQLType.IsJson() {
if rawValueType.Kind() == reflect.String {
hasAssigned = true
x := reflect.New(fieldType)
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
} else if rawValueType.Kind() == reflect.Slice {
hasAssigned = true
x := reflect.New(fieldType)
if len(vv.Bytes()) > 0 {
if col.SQLType.IsText() {
x := reflect.New(fieldType)
err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
}
} else if session.statement.UseCascade {
table, err := session.engine.autoMapType(*fieldValue)
if err != nil {
return nil, err
}
hasAssigned = true
if len(table.PrimaryKeys) != 1 {
return nil, errors.New("unsupported non or composited primary key cascade")
}
var pk = make(core.PK, len(table.PrimaryKeys))
pk[0], err = asKind(vv, rawValueType)
if err != nil {
return nil, err
}
if !isPKZero(pk) {
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
// property to be fetched lazily
structInter := reflect.New(fieldValue.Type())
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
if err != nil {
return nil, err
}
if has {
fieldValue.Set(structInter.Elem())
} else {
return nil, errors.New("cascade obj is not exist")
if fieldValue.Len() > 0 {
for i := 0; i < fieldValue.Len(); i++ {
if i < vv.Len() {
fieldValue.Index(i).Set(vv.Index(i))
}
}
} else {
for i := 0; i < vv.Len(); i++ {
fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i)))
}
}
}
}
}
case reflect.Ptr:
// !nashtsai! TODO merge duplicated codes above
switch fieldType {
// following types case matching ptr's native type, therefore assign ptr directly
case core.PtrStringType:
if rawValueType.Kind() == reflect.String {
x := vv.String()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrBoolType:
if rawValueType.Kind() == reflect.Bool {
x := vv.Bool()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrTimeType:
if rawValueType == core.PtrTimeType {
hasAssigned = true
var x = rawValue.Interface().(time.Time)
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrFloat64Type:
if rawValueType.Kind() == reflect.Float64 {
x := vv.Float()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUint64Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint64(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt64Type:
if rawValueType.Kind() == reflect.Int64 {
x := vv.Int()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrFloat32Type:
if rawValueType.Kind() == reflect.Float64 {
var x = float32(vv.Float())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrIntType:
if rawValueType.Kind() == reflect.Int64 {
var x = int(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUintType:
if rawValueType.Kind() == reflect.Int64 {
var x = uint(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUint32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Complex64Type:
var x complex64
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
case core.Complex128Type:
var x complex128
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
} // switch fieldType
} // switch fieldType.Kind()
}
case reflect.String:
if rawValueType.Kind() == reflect.String {
hasAssigned = true
fieldValue.SetString(vv.String())
}
case reflect.Bool:
if rawValueType.Kind() == reflect.Bool {
hasAssigned = true
fieldValue.SetBool(vv.Bool())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch rawValueType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
hasAssigned = true
fieldValue.SetInt(vv.Int())
}
case reflect.Float32, reflect.Float64:
switch rawValueType.Kind() {
case reflect.Float32, reflect.Float64:
hasAssigned = true
fieldValue.SetFloat(vv.Float())
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
switch rawValueType.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
hasAssigned = true
fieldValue.SetUint(vv.Uint())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
hasAssigned = true
fieldValue.SetUint(uint64(vv.Int()))
}
case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) {
dbTZ := session.engine.DatabaseTZ
if col.TimeZone != nil {
dbTZ = col.TimeZone
}
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value
if !hasAssigned {
data, err := value2Bytes(&rawValue)
if rawValueType == core.TimeType {
hasAssigned = true
t := vv.Convert(core.TimeType).Interface().(time.Time)
z, _ := t.Zone()
// set new location if database don't save timezone or give an incorrect timezone
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location
session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
t.Minute(), t.Second(), t.Nanosecond(), dbTZ)
}
t = t.In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else if rawValueType == core.IntType || rawValueType == core.Int64Type ||
rawValueType == core.Int32Type {
hasAssigned = true
t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else {
if d, ok := vv.Interface().([]uint8); ok {
hasAssigned = true
t, err := session.byte2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else if d, ok := vv.Interface().(string); ok {
hasAssigned = true
t, err := session.str2Time(col, d)
if err != nil {
session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else {
return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface())
}
}
} else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
// !<winxxp>! 增加支持sql.Scanner接口的结构如sql.NullString
hasAssigned = true
if err := nulVal.Scan(vv.Interface()); err != nil {
session.engine.logger.Error("sql.Sanner error:", err.Error())
hasAssigned = false
}
} else if col.SQLType.IsJson() {
if rawValueType.Kind() == reflect.String {
hasAssigned = true
x := reflect.New(fieldType)
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
} else if rawValueType.Kind() == reflect.Slice {
hasAssigned = true
x := reflect.New(fieldType)
if len(vv.Bytes()) > 0 {
err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil {
return nil, err
}
fieldValue.Set(x.Elem())
}
}
} else if session.statement.UseCascade {
table, err := session.engine.autoMapType(*fieldValue)
if err != nil {
return nil, err
}
if err = session.bytes2Value(col, fieldValue, data); err != nil {
hasAssigned = true
if len(table.PrimaryKeys) != 1 {
return nil, errors.New("unsupported non or composited primary key cascade")
}
var pk = make(core.PK, len(table.PrimaryKeys))
pk[0], err = asKind(vv, rawValueType)
if err != nil {
return nil, err
}
if !isPKZero(pk) {
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
// property to be fetched lazily
structInter := reflect.New(fieldValue.Type())
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
if err != nil {
return nil, err
}
if has {
fieldValue.Set(structInter.Elem())
} else {
return nil, errors.New("cascade obj is not exist")
}
}
}
case reflect.Ptr:
// !nashtsai! TODO merge duplicated codes above
switch fieldType {
// following types case matching ptr's native type, therefore assign ptr directly
case core.PtrStringType:
if rawValueType.Kind() == reflect.String {
x := vv.String()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrBoolType:
if rawValueType.Kind() == reflect.Bool {
x := vv.Bool()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrTimeType:
if rawValueType == core.PtrTimeType {
hasAssigned = true
var x = rawValue.Interface().(time.Time)
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrFloat64Type:
if rawValueType.Kind() == reflect.Float64 {
x := vv.Float()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUint64Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint64(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt64Type:
if rawValueType.Kind() == reflect.Int64 {
x := vv.Int()
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrFloat32Type:
if rawValueType.Kind() == reflect.Float64 {
var x = float32(vv.Float())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrIntType:
if rawValueType.Kind() == reflect.Int64 {
var x = int(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrInt16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = int16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUintType:
if rawValueType.Kind() == reflect.Int64 {
var x = uint(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.PtrUint32Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint32(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint8Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint8(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Uint16Type:
if rawValueType.Kind() == reflect.Int64 {
var x = uint16(vv.Int())
hasAssigned = true
fieldValue.Set(reflect.ValueOf(&x))
}
case core.Complex64Type:
var x complex64
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
case core.Complex128Type:
var x complex128
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
return nil, err
}
fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
} // switch fieldType
} // switch fieldType.Kind()
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value
if !hasAssigned {
data, err := value2Bytes(&rawValue)
if err != nil {
return nil, err
}
if err = session.bytes2Value(col, fieldValue, data); err != nil {
return nil, err
}
}
}
@ -828,15 +834,6 @@ func (session *Session) LastSQL() (string, []interface{}) {
return session.lastSQL, session.lastSQLArgs
}
// tbName get some table's table name
func (session *Session) tbNameNoSchema(table *core.Table) string {
if len(session.statement.AltTableName) > 0 {
return session.statement.AltTableName
}
return table.Name
}
// Unscoped always disable struct tag "deleted"
func (session *Session) Unscoped() *Session {
session.statement.Unscoped()

View File

@ -4,6 +4,121 @@
package xorm
import (
"reflect"
"strings"
"time"
"github.com/go-xorm/core"
)
type incrParam struct {
colName string
arg interface{}
}
type decrParam struct {
colName string
arg interface{}
}
type exprParam struct {
colName string
expr string
}
type columnMap []string
func (m columnMap) contain(colName string) bool {
if len(m) == 0 {
return false
}
n := len(colName)
for _, mk := range m {
if len(mk) != n {
continue
}
if strings.EqualFold(mk, colName) {
return true
}
}
return false
}
func (m *columnMap) add(colName string) bool {
if m.contain(colName) {
return false
}
*m = append(*m, colName)
return true
}
func setColumnInt(bean interface{}, col *core.Column, t int64) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t)
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t))
}
}
}
func setColumnTime(bean interface{}, col *core.Column, t time.Time) {
v, err := col.ValueOf(bean)
if err != nil {
return
}
if v.CanSet() {
switch v.Type().Kind() {
case reflect.Struct:
v.Set(reflect.ValueOf(t).Convert(v.Type()))
case reflect.Int, reflect.Int64, reflect.Int32:
v.SetInt(t.Unix())
case reflect.Uint, reflect.Uint64, reflect.Uint32:
v.SetUint(uint64(t.Unix()))
}
}
}
func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) {
if len(m) == 0 {
return false, false
}
n := len(col.Name)
for mk := range m {
if len(mk) != n {
continue
}
if strings.EqualFold(mk, col.Name) {
return m[mk], true
}
}
return false, false
}
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
}
// Incr provides a query string like "count = count + 1"
func (session *Session) Incr(column string, arg ...interface{}) *Session {
session.statement.Incr(column, arg...)

View File

@ -27,7 +27,7 @@ func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string,
return ErrCacheFailed
}
cacher := session.engine.getCacher2(table)
cacher := session.engine.getCacher(tableName)
pkColumns := table.PKColumns()
ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil {
@ -79,7 +79,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
defer session.Close()
}
if err := session.statement.setRefValue(rValue(bean)); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return 0, err
}
@ -199,7 +199,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
})
}
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
if cacher := session.engine.getCacher(tableName); cacher != nil && session.statement.UseCache {
session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...)
}

View File

@ -57,7 +57,7 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) {
}
if beanValue.Elem().Kind() == reflect.Struct {
if err := session.statement.setRefValue(beanValue.Elem()); err != nil {
if err := session.statement.setRefBean(bean[0]); err != nil {
return false, err
}
}

View File

@ -29,6 +29,39 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{})
return session.find(rowsSlicePtr, condiBean...)
}
// FindAndCount find the results and also return the counts
func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...interface{}) (int64, error) {
if session.isAutoClose {
defer session.Close()
}
session.autoResetStatement = false
err := session.find(rowsSlicePtr, condiBean...)
if err != nil {
return 0, err
}
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map {
return 0, errors.New("needs a pointer to a slice or a map")
}
sliceElementType := sliceValue.Type().Elem()
if sliceElementType.Kind() == reflect.Ptr {
sliceElementType = sliceElementType.Elem()
}
session.autoResetStatement = true
if session.statement.selectStr != "" {
session.statement.selectStr = ""
}
if session.statement.OrderStr != "" {
session.statement.OrderStr = ""
}
return session.Count(reflect.New(sliceElementType).Interface())
}
func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map {
@ -42,7 +75,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
if sliceElementType.Kind() == reflect.Ptr {
if sliceElementType.Elem().Kind() == reflect.Struct {
pv := reflect.New(sliceElementType.Elem())
if err := session.statement.setRefValue(pv.Elem()); err != nil {
if err := session.statement.setRefValue(pv); err != nil {
return err
}
} else {
@ -50,7 +83,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
}
} else if sliceElementType.Kind() == reflect.Struct {
pv := reflect.New(sliceElementType)
if err := session.statement.setRefValue(pv.Elem()); err != nil {
if err := session.statement.setRefValue(pv); err != nil {
return err
}
} else {
@ -128,7 +161,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
}
args = append(session.statement.joinArgs, condArgs...)
sqlStr, err = session.statement.genSelectSQL(columnStr, condSQL)
sqlStr, err = session.statement.genSelectSQL(columnStr, condSQL, true, true)
if err != nil {
return err
}
@ -143,7 +176,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
}
if session.canCache() {
if cacher := session.engine.getCacher2(table); cacher != nil &&
if cacher := session.engine.getCacher(table.Name); cacher != nil &&
!session.statement.IsDistinct &&
!session.statement.unscoped {
err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...)
@ -288,6 +321,12 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
return ErrCacheFailed
}
tableName := session.statement.TableName()
cacher := session.engine.getCacher(tableName)
if cacher == nil {
return nil
}
for _, filter := range session.engine.dialect.Filters() {
sqlStr = filter.Do(sqlStr, session.engine.dialect, session.statement.RefTable)
}
@ -297,9 +336,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
return ErrCacheFailed
}
tableName := session.statement.TableName()
table := session.statement.RefTable
cacher := session.engine.getCacher2(table)
ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil {
rows, err := session.queryRows(newsql, args...)

View File

@ -31,7 +31,7 @@ func (session *Session) get(bean interface{}) (bool, error) {
}
if beanValue.Elem().Kind() == reflect.Struct {
if err := session.statement.setRefValue(beanValue.Elem()); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return false, err
}
}
@ -57,7 +57,7 @@ func (session *Session) get(bean interface{}) (bool, error) {
table := session.statement.RefTable
if session.canCache() && beanValue.Elem().Kind() == reflect.Struct {
if cacher := session.engine.getCacher2(table); cacher != nil &&
if cacher := session.engine.getCacher(table.Name); cacher != nil &&
!session.statement.unscoped {
has, err := session.cacheGet(bean, sqlStr, args...)
if err != ErrCacheFailed {
@ -134,8 +134,9 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf
return false, ErrCacheFailed
}
cacher := session.engine.getCacher2(session.statement.RefTable)
tableName := session.statement.TableName()
cacher := session.engine.getCacher(tableName)
session.engine.logger.Debug("[cacheGet] find sql:", newsql, args)
table := session.statement.RefTable
ids, err := core.GetCacheSql(cacher, tableName, newsql, args)

View File

@ -66,11 +66,12 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
return 0, errors.New("could not insert a empty slice")
}
if err := session.statement.setRefValue(reflect.ValueOf(sliceValue.Index(0).Interface())); err != nil {
if err := session.statement.setRefBean(sliceValue.Index(0).Interface()); err != nil {
return 0, err
}
if len(session.statement.TableName()) <= 0 {
tableName := session.statement.TableName()
if len(tableName) <= 0 {
return 0, ErrTableNotFound
}
@ -115,15 +116,11 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
if col.IsDeleted {
continue
}
if session.statement.ColumnStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
}
if session.statement.omitColumnMap.contain(col.Name) {
continue
}
if session.statement.OmitStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue
}
if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) {
continue
}
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
val, t := session.engine.nowTime(col)
@ -170,15 +167,11 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
if col.IsDeleted {
continue
}
if session.statement.ColumnStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
}
if session.statement.omitColumnMap.contain(col.Name) {
continue
}
if session.statement.OmitStr != "" {
if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue
}
if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) {
continue
}
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
val, t := session.engine.nowTime(col)
@ -211,38 +204,33 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
}
cleanupProcessorsClosures(&session.beforeClosures)
var sql = "INSERT INTO %s (%v%v%v) VALUES (%v)"
var statement string
var tableName = session.statement.TableName()
var sql string
if session.engine.dialect.DBType() == core.ORACLE {
sql = "INSERT ALL INTO %s (%v%v%v) VALUES (%v) SELECT 1 FROM DUAL"
temp := fmt.Sprintf(") INTO %s (%v%v%v) VALUES (",
session.engine.Quote(tableName),
session.engine.QuoteStr(),
strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
session.engine.QuoteStr())
statement = fmt.Sprintf(sql,
sql = fmt.Sprintf("INSERT ALL INTO %s (%v%v%v) VALUES (%v) SELECT 1 FROM DUAL",
session.engine.Quote(tableName),
session.engine.QuoteStr(),
strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
session.engine.QuoteStr(),
strings.Join(colMultiPlaces, temp))
} else {
statement = fmt.Sprintf(sql,
sql = fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)",
session.engine.Quote(tableName),
session.engine.QuoteStr(),
strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
session.engine.QuoteStr(),
strings.Join(colMultiPlaces, "),("))
}
res, err := session.exec(statement, args...)
res, err := session.exec(sql, args...)
if err != nil {
return 0, err
}
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
session.cacheInsert(table, tableName)
}
session.cacheInsert(tableName)
lenAfterClosures := len(session.afterClosures)
for i := 0; i < size; i++ {
@ -298,7 +286,7 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) {
}
func (session *Session) innerInsert(bean interface{}) (int64, error) {
if err := session.statement.setRefValue(rValue(bean)); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return 0, err
}
if len(session.statement.TableName()) <= 0 {
@ -316,8 +304,8 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
if processor, ok := interface{}(bean).(BeforeInsertProcessor); ok {
processor.BeforeInsert()
}
// --
colNames, args, err := genCols(session.statement.RefTable, session, bean, false, false)
colNames, args, err := session.genInsertColumns(bean)
if err != nil {
return 0, err
}
@ -402,9 +390,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
defer handleAfterInsertProcessorFunc(bean)
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
session.cacheInsert(table, tableName)
}
session.cacheInsert(tableName)
if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean)
@ -447,9 +433,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
}
defer handleAfterInsertProcessorFunc(bean)
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
session.cacheInsert(table, tableName)
}
session.cacheInsert(tableName)
if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean)
@ -490,9 +474,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
defer handleAfterInsertProcessorFunc(bean)
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
session.cacheInsert(table, tableName)
}
session.cacheInsert(tableName)
if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean)
@ -539,16 +521,104 @@ func (session *Session) InsertOne(bean interface{}) (int64, error) {
return session.innerInsert(bean)
}
func (session *Session) cacheInsert(table *core.Table, tables ...string) error {
if table == nil {
return ErrCacheFailed
func (session *Session) cacheInsert(table string) error {
if !session.statement.UseCache {
return nil
}
cacher := session.engine.getCacher2(table)
for _, t := range tables {
session.engine.logger.Debug("[cache] clear sql:", t)
cacher.ClearIds(t)
cacher := session.engine.getCacher(table)
if cacher == nil {
return nil
}
session.engine.logger.Debug("[cache] clear sql:", table)
cacher.ClearIds(table)
return nil
}
// genInsertColumns generates insert needed columns
func (session *Session) genInsertColumns(bean interface{}) ([]string, []interface{}, error) {
table := session.statement.RefTable
colNames := make([]string, 0, len(table.ColumnsSeq()))
args := make([]interface{}, 0, len(table.ColumnsSeq()))
for _, col := range table.Columns() {
if col.MapType == core.ONLYFROMDB {
continue
}
if col.IsDeleted {
continue
}
if session.statement.omitColumnMap.contain(col.Name) {
continue
}
if len(session.statement.columnMap) > 0 && !session.statement.columnMap.contain(col.Name) {
continue
}
if _, ok := session.statement.incrColumns[col.Name]; ok {
continue
} else if _, ok := session.statement.decrColumns[col.Name]; ok {
continue
}
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return nil, nil, err
}
fieldValue := *fieldValuePtr
if col.IsAutoIncrement {
switch fieldValue.Type().Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
if fieldValue.Int() == 0 {
continue
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
if fieldValue.Uint() == 0 {
continue
}
case reflect.String:
if len(fieldValue.String()) == 0 {
continue
}
case reflect.Ptr:
if fieldValue.Pointer() == 0 {
continue
}
}
}
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int
fieldValue = reflect.ValueOf(nilValue)
}
}
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
} else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
} else {
arg, err := session.value2Interface(col, fieldValue)
if err != nil {
return colNames, args, err
}
args = append(args, arg)
}
colNames = append(colNames, col.Name)
}
return colNames, args, nil
}

View File

@ -64,13 +64,17 @@ func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interfa
}
}
if err := session.statement.processIDParam(); err != nil {
return "", nil, err
}
condSQL, condArgs, err := builder.ToSQL(session.statement.cond)
if err != nil {
return "", nil, err
}
args := append(session.statement.joinArgs, condArgs...)
sqlStr, err := session.statement.genSelectSQL(columnStr, condSQL)
sqlStr, err := session.statement.genSelectSQL(columnStr, condSQL, true, true)
if err != nil {
return "", nil, err
}

View File

@ -6,9 +6,7 @@ package xorm
import (
"database/sql"
"errors"
"fmt"
"reflect"
"strings"
"github.com/go-xorm/core"
@ -34,8 +32,7 @@ func (session *Session) CreateTable(bean interface{}) error {
}
func (session *Session) createTable(bean interface{}) error {
v := rValue(bean)
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
@ -54,8 +51,7 @@ func (session *Session) CreateIndexes(bean interface{}) error {
}
func (session *Session) createIndexes(bean interface{}) error {
v := rValue(bean)
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
@ -78,8 +74,7 @@ func (session *Session) CreateUniques(bean interface{}) error {
}
func (session *Session) createUniques(bean interface{}) error {
v := rValue(bean)
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
@ -103,8 +98,7 @@ func (session *Session) DropIndexes(bean interface{}) error {
}
func (session *Session) dropIndexes(bean interface{}) error {
v := rValue(bean)
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return err
}
@ -128,11 +122,7 @@ func (session *Session) DropTable(beanOrTableName interface{}) error {
}
func (session *Session) dropTable(beanOrTableName interface{}) error {
tableName, err := session.engine.tableName(beanOrTableName)
if err != nil {
return err
}
tableName := session.engine.TableName(beanOrTableName)
var needDrop = true
if !session.engine.dialect.SupportDropIfExists() {
sqlStr, args := session.engine.dialect.TableCheckSql(tableName)
@ -144,8 +134,8 @@ func (session *Session) dropTable(beanOrTableName interface{}) error {
}
if needDrop {
sqlStr := session.engine.Dialect().DropTableSql(tableName)
_, err = session.exec(sqlStr)
sqlStr := session.engine.Dialect().DropTableSql(session.engine.TableName(tableName, true))
_, err := session.exec(sqlStr)
return err
}
return nil
@ -157,10 +147,7 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error)
defer session.Close()
}
tableName, err := session.engine.tableName(beanOrTableName)
if err != nil {
return false, err
}
tableName := session.engine.TableName(beanOrTableName)
return session.isTableExist(tableName)
}
@ -173,24 +160,15 @@ func (session *Session) isTableExist(tableName string) (bool, error) {
// IsTableEmpty if table have any records
func (session *Session) IsTableEmpty(bean interface{}) (bool, error) {
v := rValue(bean)
t := v.Type()
if t.Kind() == reflect.String {
if session.isAutoClose {
defer session.Close()
}
return session.isTableEmpty(bean.(string))
} else if t.Kind() == reflect.Struct {
rows, err := session.Count(bean)
return rows == 0, err
if session.isAutoClose {
defer session.Close()
}
return false, errors.New("bean should be a struct or struct's point")
return session.isTableEmpty(session.engine.TableName(bean))
}
func (session *Session) isTableEmpty(tableName string) (bool, error) {
var total int64
sqlStr := fmt.Sprintf("select count(*) from %s", session.engine.Quote(tableName))
sqlStr := fmt.Sprintf("select count(*) from %s", session.engine.Quote(session.engine.TableName(tableName, true)))
err := session.queryRow(sqlStr).Scan(&total)
if err != nil {
if err == sql.ErrNoRows {
@ -255,6 +233,12 @@ func (session *Session) Sync2(beans ...interface{}) error {
return err
}
session.autoResetStatement = false
defer func() {
session.autoResetStatement = true
session.resetStatement()
}()
var structTables []*core.Table
for _, bean := range beans {
@ -264,7 +248,8 @@ func (session *Session) Sync2(beans ...interface{}) error {
return err
}
structTables = append(structTables, table)
var tbName = session.tbNameNoSchema(table)
tbName := engine.TableName(bean)
tbNameWithSchema := engine.TableName(tbName, true)
var oriTable *core.Table
for _, tb := range tables {
@ -309,32 +294,32 @@ func (session *Session) Sync2(beans ...interface{}) error {
if engine.dialect.DBType() == core.MYSQL ||
engine.dialect.DBType() == core.POSTGRES {
engine.logger.Infof("Table %s column %s change type from %s to %s\n",
tbName, col.Name, curType, expectedType)
_, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col))
tbNameWithSchema, col.Name, curType, expectedType)
_, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col))
} else {
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n",
tbName, col.Name, curType, expectedType)
tbNameWithSchema, col.Name, curType, expectedType)
}
} else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) {
if engine.dialect.DBType() == core.MYSQL {
if oriCol.Length < col.Length {
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n",
tbName, col.Name, oriCol.Length, col.Length)
_, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col))
tbNameWithSchema, col.Name, oriCol.Length, col.Length)
_, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col))
}
}
} else {
if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') {
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s",
tbName, col.Name, curType, expectedType)
tbNameWithSchema, col.Name, curType, expectedType)
}
}
} else if expectedType == core.Varchar {
if engine.dialect.DBType() == core.MYSQL {
if oriCol.Length < col.Length {
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n",
tbName, col.Name, oriCol.Length, col.Length)
_, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col))
tbNameWithSchema, col.Name, oriCol.Length, col.Length)
_, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col))
}
}
}
@ -348,7 +333,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
}
} else {
session.statement.RefTable = table
session.statement.tableName = tbName
session.statement.tableName = tbNameWithSchema
err = session.addColumn(col.Name)
}
if err != nil {
@ -371,7 +356,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
if oriIndex != nil {
if oriIndex.Type != index.Type {
sql := engine.dialect.DropIndexSql(tbName, oriIndex)
sql := engine.dialect.DropIndexSql(tbNameWithSchema, oriIndex)
_, err = session.exec(sql)
if err != nil {
return err
@ -387,7 +372,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
for name2, index2 := range oriTable.Indexes {
if _, ok := foundIndexNames[name2]; !ok {
sql := engine.dialect.DropIndexSql(tbName, index2)
sql := engine.dialect.DropIndexSql(tbNameWithSchema, index2)
_, err = session.exec(sql)
if err != nil {
return err
@ -398,12 +383,12 @@ func (session *Session) Sync2(beans ...interface{}) error {
for name, index := range addedNames {
if index.Type == core.UniqueType {
session.statement.RefTable = table
session.statement.tableName = tbName
err = session.addUnique(tbName, name)
session.statement.tableName = tbNameWithSchema
err = session.addUnique(tbNameWithSchema, name)
} else if index.Type == core.IndexType {
session.statement.RefTable = table
session.statement.tableName = tbName
err = session.addIndex(tbName, name)
session.statement.tableName = tbNameWithSchema
err = session.addIndex(tbNameWithSchema, name)
}
if err != nil {
return err
@ -428,7 +413,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
for _, colName := range table.ColumnsSeq() {
if oriTable.GetColumn(colName) == nil {
engine.logger.Warnf("Table %s has column %s but struct has not related field", table.Name, colName)
engine.logger.Warnf("Table %s has column %s but struct has not related field", engine.TableName(table.Name, true), colName)
}
}
}

View File

@ -24,6 +24,7 @@ func (session *Session) Rollback() error {
if !session.isAutoCommit && !session.isCommitedOrRollbacked {
session.saveLastSQL(session.engine.dialect.RollBackStr())
session.isCommitedOrRollbacked = true
session.isAutoCommit = true
return session.tx.Rollback()
}
return nil
@ -34,6 +35,7 @@ func (session *Session) Commit() error {
if !session.isAutoCommit && !session.isCommitedOrRollbacked {
session.saveLastSQL("COMMIT")
session.isCommitedOrRollbacked = true
session.isAutoCommit = true
var err error
if err = session.tx.Commit(); err == nil {
// handle processors after tx committed

View File

@ -40,7 +40,7 @@ func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string,
}
}
cacher := session.engine.getCacher2(table)
cacher := session.engine.getCacher(tableName)
session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:])
ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:])
if err != nil {
@ -167,7 +167,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
var isMap = t.Kind() == reflect.Map
var isStruct = t.Kind() == reflect.Struct
if isStruct {
if err := session.statement.setRefValue(v); err != nil {
if err := session.statement.setRefBean(bean); err != nil {
return 0, err
}
@ -176,12 +176,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
}
if session.statement.ColumnStr == "" {
colNames, args = buildUpdates(session.engine, session.statement.RefTable, bean, false, false,
false, false, session.statement.allUseBool, session.statement.useAllCols,
session.statement.mustColumnMap, session.statement.nullableMap,
session.statement.columnMap, true, session.statement.unscoped)
colNames, args = session.statement.buildUpdates(bean, false, false,
false, false, true)
} else {
colNames, args, err = genCols(session.statement.RefTable, session, bean, true, true)
colNames, args, err = session.genUpdateColumns(bean)
if err != nil {
return 0, err
}
@ -202,7 +200,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
table := session.statement.RefTable
if session.statement.UseAutoTime && table != nil && table.Updated != "" {
if _, ok := session.statement.columnMap[strings.ToLower(table.Updated)]; !ok {
if !session.statement.columnMap.contain(table.Updated) &&
!session.statement.omitColumnMap.contain(table.Updated) {
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
col := table.UpdatedColumn()
val, t := session.engine.nowTime(col)
@ -362,12 +361,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
}
}
if table != nil {
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
//session.cacheUpdate(table, tableName, sqlStr, args...)
cacher.ClearIds(tableName)
cacher.ClearBeans(tableName)
}
if cacher := session.engine.getCacher(tableName); cacher != nil && session.statement.UseCache {
//session.cacheUpdate(table, tableName, sqlStr, args...)
session.engine.logger.Debug("[cacheUpdate] clear table ", tableName)
cacher.ClearIds(tableName)
cacher.ClearBeans(tableName)
}
// handle after update processors
@ -402,3 +400,92 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
return res.RowsAffected()
}
func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interface{}, error) {
table := session.statement.RefTable
colNames := make([]string, 0, len(table.ColumnsSeq()))
args := make([]interface{}, 0, len(table.ColumnsSeq()))
for _, col := range table.Columns() {
if !col.IsVersion && !col.IsCreated && !col.IsUpdated {
if session.statement.omitColumnMap.contain(col.Name) {
continue
}
}
if col.MapType == core.ONLYFROMDB {
continue
}
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return nil, nil, err
}
fieldValue := *fieldValuePtr
if col.IsAutoIncrement {
switch fieldValue.Type().Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
if fieldValue.Int() == 0 {
continue
}
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
if fieldValue.Uint() == 0 {
continue
}
case reflect.String:
if len(fieldValue.String()) == 0 {
continue
}
case reflect.Ptr:
if fieldValue.Pointer() == 0 {
continue
}
}
}
if col.IsDeleted || col.IsCreated {
continue
}
if len(session.statement.columnMap) > 0 {
if !session.statement.columnMap.contain(col.Name) {
continue
} else if _, ok := session.statement.incrColumns[col.Name]; ok {
continue
} else if _, ok := session.statement.decrColumns[col.Name]; ok {
continue
}
}
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int
fieldValue = reflect.ValueOf(nilValue)
}
}
if col.IsUpdated && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
} else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
} else {
arg, err := session.value2Interface(col, fieldValue)
if err != nil {
return colNames, args, err
}
args = append(args, arg)
}
colNames = append(colNames, session.engine.Quote(col.Name)+" = ?")
}
return colNames, args, nil
}

View File

@ -5,7 +5,6 @@
package xorm
import (
"bytes"
"database/sql/driver"
"encoding/json"
"errors"
@ -18,21 +17,6 @@ import (
"github.com/go-xorm/core"
)
type incrParam struct {
colName string
arg interface{}
}
type decrParam struct {
colName string
arg interface{}
}
type exprParam struct {
colName string
expr string
}
// Statement save all the sql info for executing SQL
type Statement struct {
RefTable *core.Table
@ -47,7 +31,6 @@ type Statement struct {
HavingStr string
ColumnStr string
selectStr string
columnMap map[string]bool
useAllCols bool
OmitStr string
AltTableName string
@ -67,6 +50,8 @@ type Statement struct {
allUseBool bool
checkVersion bool
unscoped bool
columnMap columnMap
omitColumnMap columnMap
mustColumnMap map[string]bool
nullableMap map[string]bool
incrColumns map[string]incrParam
@ -89,7 +74,8 @@ func (statement *Statement) Init() {
statement.HavingStr = ""
statement.ColumnStr = ""
statement.OmitStr = ""
statement.columnMap = make(map[string]bool)
statement.columnMap = columnMap{}
statement.omitColumnMap = columnMap{}
statement.AltTableName = ""
statement.tableName = ""
statement.idParam = nil
@ -221,34 +207,33 @@ func (statement *Statement) setRefValue(v reflect.Value) error {
if err != nil {
return err
}
statement.tableName = statement.Engine.tbName(v)
statement.tableName = statement.Engine.TableName(v, true)
return nil
}
// Table tempororily set table name, the parameter could be a string or a pointer of struct
func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
v := rValue(tableNameOrBean)
t := v.Type()
if t.Kind() == reflect.String {
statement.AltTableName = tableNameOrBean.(string)
} else if t.Kind() == reflect.Struct {
var err error
statement.RefTable, err = statement.Engine.autoMapType(v)
if err != nil {
statement.Engine.logger.Error(err)
return statement
}
statement.AltTableName = statement.Engine.tbName(v)
func (statement *Statement) setRefBean(bean interface{}) error {
var err error
statement.RefTable, err = statement.Engine.autoMapType(rValue(bean))
if err != nil {
return err
}
return statement
statement.tableName = statement.Engine.TableName(bean, true)
return nil
}
// Auto generating update columnes and values according a struct
func buildUpdates(engine *Engine, table *core.Table, bean interface{},
includeVersion bool, includeUpdated bool, includeNil bool,
includeAutoIncr bool, allUseBool bool, useAllCols bool,
mustColumnMap map[string]bool, nullableMap map[string]bool,
columnMap map[string]bool, update, unscoped bool) ([]string, []interface{}) {
func (statement *Statement) buildUpdates(bean interface{},
includeVersion, includeUpdated, includeNil,
includeAutoIncr, update bool) ([]string, []interface{}) {
engine := statement.Engine
table := statement.RefTable
allUseBool := statement.allUseBool
useAllCols := statement.useAllCols
mustColumnMap := statement.mustColumnMap
nullableMap := statement.nullableMap
columnMap := statement.columnMap
omitColumnMap := statement.omitColumnMap
unscoped := statement.unscoped
var colNames = make([]string, 0)
var args = make([]interface{}, 0)
@ -268,7 +253,14 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{},
if col.IsDeleted && !unscoped {
continue
}
if use, ok := columnMap[strings.ToLower(col.Name)]; ok && !use {
if omitColumnMap.contain(col.Name) {
continue
}
if len(columnMap) > 0 && !columnMap.contain(col.Name) {
continue
}
if col.MapType == core.ONLYFROMDB {
continue
}
@ -604,17 +596,10 @@ func (statement *Statement) col2NewColsWithQuote(columns ...string) []string {
}
func (statement *Statement) colmap2NewColsWithQuote() []string {
newColumns := make([]string, 0, len(statement.columnMap))
for col := range statement.columnMap {
fields := strings.Split(strings.TrimSpace(col), ".")
if len(fields) == 1 {
newColumns = append(newColumns, statement.Engine.quote(fields[0]))
} else if len(fields) == 2 {
newColumns = append(newColumns, statement.Engine.quote(fields[0])+"."+
statement.Engine.quote(fields[1]))
} else {
panic(errors.New("unwanted colnames"))
}
newColumns := make([]string, len(statement.columnMap), len(statement.columnMap))
copy(newColumns, statement.columnMap)
for i := 0; i < len(statement.columnMap); i++ {
newColumns[i] = statement.Engine.Quote(newColumns[i])
}
return newColumns
}
@ -642,10 +627,11 @@ func (statement *Statement) Select(str string) *Statement {
func (statement *Statement) Cols(columns ...string) *Statement {
cols := col2NewCols(columns...)
for _, nc := range cols {
statement.columnMap[strings.ToLower(nc)] = true
statement.columnMap.add(nc)
}
newColumns := statement.colmap2NewColsWithQuote()
statement.ColumnStr = strings.Join(newColumns, ", ")
statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.quote("*"), "*", -1)
return statement
@ -680,7 +666,7 @@ func (statement *Statement) UseBool(columns ...string) *Statement {
func (statement *Statement) Omit(columns ...string) {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.columnMap[strings.ToLower(nc)] = false
statement.omitColumnMap = append(statement.omitColumnMap, nc)
}
statement.OmitStr = statement.Engine.Quote(strings.Join(newColumns, statement.Engine.Quote(", ")))
}
@ -719,10 +705,9 @@ func (statement *Statement) OrderBy(order string) *Statement {
// Desc generate `ORDER BY xx DESC`
func (statement *Statement) Desc(colNames ...string) *Statement {
var buf bytes.Buffer
fmt.Fprintf(&buf, statement.OrderStr)
var buf builder.StringBuilder
if len(statement.OrderStr) > 0 {
fmt.Fprint(&buf, ", ")
fmt.Fprint(&buf, statement.OrderStr, ", ")
}
newColNames := statement.col2NewColsWithQuote(colNames...)
fmt.Fprintf(&buf, "%v DESC", strings.Join(newColNames, " DESC, "))
@ -732,10 +717,9 @@ func (statement *Statement) Desc(colNames ...string) *Statement {
// Asc provide asc order by query condition, the input parameters are columns.
func (statement *Statement) Asc(colNames ...string) *Statement {
var buf bytes.Buffer
fmt.Fprintf(&buf, statement.OrderStr)
var buf builder.StringBuilder
if len(statement.OrderStr) > 0 {
fmt.Fprint(&buf, ", ")
fmt.Fprint(&buf, statement.OrderStr, ", ")
}
newColNames := statement.col2NewColsWithQuote(colNames...)
fmt.Fprintf(&buf, "%v ASC", strings.Join(newColNames, " ASC, "))
@ -743,48 +727,35 @@ func (statement *Statement) Asc(colNames ...string) *Statement {
return statement
}
// Table tempororily set table name, the parameter could be a string or a pointer of struct
func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
v := rValue(tableNameOrBean)
t := v.Type()
if t.Kind() == reflect.Struct {
var err error
statement.RefTable, err = statement.Engine.autoMapType(v)
if err != nil {
statement.Engine.logger.Error(err)
return statement
}
}
statement.AltTableName = statement.Engine.TableName(tableNameOrBean, true)
return statement
}
// 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 bytes.Buffer
var buf builder.StringBuilder
if len(statement.JoinStr) > 0 {
fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP)
} else {
fmt.Fprintf(&buf, "%v JOIN ", joinOP)
}
switch tablename.(type) {
case []string:
t := tablename.([]string)
if len(t) > 1 {
fmt.Fprintf(&buf, "%v AS %v", statement.Engine.Quote(t[0]), statement.Engine.Quote(t[1]))
} else if len(t) == 1 {
fmt.Fprintf(&buf, statement.Engine.Quote(t[0]))
}
case []interface{}:
t := tablename.([]interface{})
l := len(t)
var table string
if l > 0 {
f := t[0]
v := rValue(f)
t := v.Type()
if t.Kind() == reflect.String {
table = f.(string)
} else if t.Kind() == reflect.Struct {
table = statement.Engine.tbName(v)
}
}
if l > 1 {
fmt.Fprintf(&buf, "%v AS %v", statement.Engine.Quote(table),
statement.Engine.Quote(fmt.Sprintf("%v", t[1])))
} else if l == 1 {
fmt.Fprintf(&buf, statement.Engine.Quote(table))
}
default:
fmt.Fprintf(&buf, statement.Engine.Quote(fmt.Sprintf("%v", tablename)))
}
tbName := statement.Engine.TableName(tablename, true)
fmt.Fprintf(&buf, " ON %v", condition)
fmt.Fprintf(&buf, "%s ON %v", tbName, condition)
statement.JoinStr = buf.String()
statement.joinArgs = append(statement.joinArgs, args...)
return statement
@ -809,18 +780,20 @@ func (statement *Statement) Unscoped() *Statement {
}
func (statement *Statement) genColumnStr() string {
var buf bytes.Buffer
if statement.RefTable == nil {
return ""
}
var buf builder.StringBuilder
columns := statement.RefTable.Columns()
for _, col := range columns {
if statement.OmitStr != "" {
if _, ok := getFlagForColumn(statement.columnMap, col); ok {
continue
}
if statement.omitColumnMap.contain(col.Name) {
continue
}
if len(statement.columnMap) > 0 && !statement.columnMap.contain(col.Name) {
continue
}
if col.MapType == core.ONLYTODB {
@ -831,10 +804,6 @@ func (statement *Statement) genColumnStr() string {
buf.WriteString(", ")
}
if col.IsPrimaryKey && statement.Engine.Dialect().DBType() == "ql" {
buf.WriteString("id() AS ")
}
if statement.JoinStr != "" {
if statement.TableAlias != "" {
buf.WriteString(statement.TableAlias)
@ -859,11 +828,13 @@ func (statement *Statement) genCreateTableSQL() string {
func (statement *Statement) genIndexSQL() []string {
var sqls []string
tbName := statement.TableName()
quote := statement.Engine.Quote
for idxName, index := range statement.RefTable.Indexes {
for _, index := range statement.RefTable.Indexes {
if index.Type == core.IndexType {
sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)),
quote(tbName), quote(strings.Join(index.Cols, quote(","))))
sql := statement.Engine.dialect.CreateIndexSql(tbName, index)
/*idxTBName := strings.Replace(tbName, ".", "_", -1)
idxTBName = strings.Replace(idxTBName, `"`, "", -1)
sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(idxTBName, idxName)),
quote(tbName), quote(strings.Join(index.Cols, quote(","))))*/
sqls = append(sqls, sql)
}
}
@ -889,16 +860,18 @@ func (statement *Statement) genUniqueSQL() []string {
func (statement *Statement) genDelIndexSQL() []string {
var sqls []string
tbName := statement.TableName()
idxPrefixName := strings.Replace(tbName, `"`, "", -1)
idxPrefixName = strings.Replace(idxPrefixName, `.`, "_", -1)
for idxName, index := range statement.RefTable.Indexes {
var rIdxName string
if index.Type == core.UniqueType {
rIdxName = uniqueName(tbName, idxName)
rIdxName = uniqueName(idxPrefixName, idxName)
} else if index.Type == core.IndexType {
rIdxName = indexName(tbName, idxName)
rIdxName = indexName(idxPrefixName, idxName)
}
sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(rIdxName))
sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(statement.Engine.TableName(rIdxName, true)))
if statement.Engine.dialect.IndexOnTable() {
sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(statement.TableName()))
sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(tbName))
}
sqls = append(sqls, sql)
}
@ -949,7 +922,7 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{},
v := rValue(bean)
isStruct := v.Kind() == reflect.Struct
if isStruct {
statement.setRefValue(v)
statement.setRefBean(bean)
}
var columnStr = statement.ColumnStr
@ -982,13 +955,17 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{},
if err := statement.mergeConds(bean); err != nil {
return "", nil, err
}
} else {
if err := statement.processIDParam(); err != nil {
return "", nil, err
}
}
condSQL, condArgs, err := builder.ToSQL(statement.cond)
if err != nil {
return "", nil, err
}
sqlStr, err := statement.genSelectSQL(columnStr, condSQL)
sqlStr, err := statement.genSelectSQL(columnStr, condSQL, true, true)
if err != nil {
return "", nil, err
}
@ -1001,7 +978,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa
var condArgs []interface{}
var err error
if len(beans) > 0 {
statement.setRefValue(rValue(beans[0]))
statement.setRefBean(beans[0])
condSQL, condArgs, err = statement.genConds(beans[0])
} else {
condSQL, condArgs, err = builder.ToSQL(statement.cond)
@ -1018,7 +995,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa
selectSQL = "count(*)"
}
}
sqlStr, err := statement.genSelectSQL(selectSQL, condSQL)
sqlStr, err := statement.genSelectSQL(selectSQL, condSQL, false, false)
if err != nil {
return "", nil, err
}
@ -1027,7 +1004,7 @@ func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interfa
}
func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) {
statement.setRefValue(rValue(bean))
statement.setRefBean(bean)
var sumStrs = make([]string, 0, len(columns))
for _, colName := range columns {
@ -1043,7 +1020,7 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri
return "", nil, err
}
sqlStr, err := statement.genSelectSQL(sumSelect, condSQL)
sqlStr, err := statement.genSelectSQL(sumSelect, condSQL, true, true)
if err != nil {
return "", nil, err
}
@ -1051,27 +1028,20 @@ func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (stri
return sqlStr, append(statement.joinArgs, condArgs...), nil
}
func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, err error) {
var distinct string
func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, needOrderBy bool) (string, error) {
var (
distinct string
dialect = statement.Engine.Dialect()
quote = statement.Engine.Quote
fromStr = " FROM "
top, mssqlCondi, whereStr string
)
if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") {
distinct = "DISTINCT "
}
var dialect = statement.Engine.Dialect()
var quote = statement.Engine.Quote
var top string
var mssqlCondi string
if err := statement.processIDParam(); err != nil {
return "", err
}
var buf bytes.Buffer
if len(condSQL) > 0 {
fmt.Fprintf(&buf, " WHERE %v", condSQL)
whereStr = " WHERE " + condSQL
}
var whereStr = buf.String()
var fromStr = " FROM "
if dialect.DBType() == core.MSSQL && strings.Contains(statement.TableName(), "..") {
fromStr += statement.TableName()
@ -1118,9 +1088,10 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, e
}
var orderStr string
if len(statement.OrderStr) > 0 {
if needOrderBy && len(statement.OrderStr) > 0 {
orderStr = " ORDER BY " + statement.OrderStr
}
var groupStr string
if len(statement.GroupByStr) > 0 {
groupStr = " GROUP BY " + statement.GroupByStr
@ -1130,45 +1101,50 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, e
}
}
// !nashtsai! REVIEW Sprintf is considered slowest mean of string concatnation, better to work with builder pattern
a = fmt.Sprintf("SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr)
var buf builder.StringBuilder
fmt.Fprintf(&buf, "SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr)
if len(mssqlCondi) > 0 {
if len(whereStr) > 0 {
a += " AND " + mssqlCondi
fmt.Fprint(&buf, " AND ", mssqlCondi)
} else {
a += " WHERE " + mssqlCondi
fmt.Fprint(&buf, " WHERE ", mssqlCondi)
}
}
if statement.GroupByStr != "" {
a = fmt.Sprintf("%v GROUP BY %v", a, statement.GroupByStr)
fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr)
}
if statement.HavingStr != "" {
a = fmt.Sprintf("%v %v", a, statement.HavingStr)
fmt.Fprint(&buf, " ", statement.HavingStr)
}
if statement.OrderStr != "" {
a = fmt.Sprintf("%v ORDER BY %v", a, statement.OrderStr)
if needOrderBy && statement.OrderStr != "" {
fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr)
}
if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE {
if statement.Start > 0 {
a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start)
} else if statement.LimitN > 0 {
a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN)
}
} else if dialect.DBType() == core.ORACLE {
if statement.Start != 0 || statement.LimitN != 0 {
a = fmt.Sprintf("SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d", columnStr, columnStr, a, statement.Start+statement.LimitN, statement.Start)
if needLimit {
if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE {
if statement.Start > 0 {
fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", statement.LimitN, statement.Start)
} else if statement.LimitN > 0 {
fmt.Fprint(&buf, " LIMIT ", statement.LimitN)
}
} else if dialect.DBType() == core.ORACLE {
if statement.Start != 0 || statement.LimitN != 0 {
oldString := buf.String()
buf.Reset()
fmt.Fprintf(&buf, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d",
columnStr, columnStr, oldString, statement.Start+statement.LimitN, statement.Start)
}
}
}
if statement.IsForUpdate {
a = dialect.ForUpdateSql(a)
return dialect.ForUpdateSql(buf.String()), nil
}
return
return buf.String(), nil
}
func (statement *Statement) processIDParam() error {
if statement.idParam == nil {
if statement.idParam == nil || statement.RefTable == nil {
return nil
}

View File

@ -17,7 +17,7 @@ import (
const (
// Version show the xorm's version
Version string = "0.6.4.0910"
Version string = "0.7.0.0504"
)
func regDrvsNDialects() bool {
@ -31,7 +31,7 @@ func regDrvsNDialects() bool {
"mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }},
"mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }},
"postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }},
"pgx": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }},
"pgx": {"postgres", func() core.Driver { return &pqDriverPgx{} }, func() core.Dialect { return &postgres{} }},
"sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }},
"oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }},
"goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }},
@ -90,6 +90,7 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
TagIdentifier: "xorm",
TZLocation: time.Local,
tagHandlers: defaultTagHandlers,
cachers: make(map[string]core.Cacher),
}
if uri.DbType == core.SQLITE {
@ -108,6 +109,13 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
return engine, nil
}
// NewEngineWithParams new a db manager with params. The params will be passed to dialect.
func NewEngineWithParams(driverName string, dataSourceName string, params map[string]string) (*Engine, error) {
engine, err := NewEngine(driverName, dataSourceName)
engine.dialect.SetParams(params)
return engine, err
}
// Clone clone an engine
func (engine *Engine) Clone() (*Engine, error) {
return NewEngine(engine.DriverName(), engine.DataSourceName())