Merge branch 'master' into 1271_share_zero

This commit is contained in:
Marcus Efraimsson
2018-03-22 16:59:36 +01:00
115 changed files with 4047 additions and 2768 deletions
+5 -1
View File
@@ -1,5 +1,6 @@
# 5.1.0 (unreleased) # 5.1.0 (unreleased)
* **MSSQL**: New Microsoft SQL Server data source [#10093](https://github.com/grafana/grafana/pull/10093), [#11298](https://github.com/grafana/grafana/pull/11298), thx [@linuxchips](https://github.com/linuxchips)
* **Prometheus**: The heatmap panel now support Prometheus histograms [#10009](https://github.com/grafana/grafana/issues/10009) * **Prometheus**: The heatmap panel now support Prometheus histograms [#10009](https://github.com/grafana/grafana/issues/10009)
* **Postgres/MySQL**: Ability to insert 0s or nulls for missing intervals [#9487](https://github.com/grafana/grafana/issues/9487), thanks [@svenklemm](https://github.com/svenklemm) * **Postgres/MySQL**: Ability to insert 0s or nulls for missing intervals [#9487](https://github.com/grafana/grafana/issues/9487), thanks [@svenklemm](https://github.com/svenklemm)
* **Graph**: Thresholds for Right Y axis [#7107](https://github.com/grafana/grafana/issues/7107), thx [@ilgizar](https://github.com/ilgizar) * **Graph**: Thresholds for Right Y axis [#7107](https://github.com/grafana/grafana/issues/7107), thx [@ilgizar](https://github.com/ilgizar)
@@ -13,9 +14,12 @@
* **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes) * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes)
* **Cloudwatch**: Support high resolution metrics [#10925](https://github.com/grafana/grafana/pull/10925), thx [@mtanda](https://github.com/mtanda) * **Cloudwatch**: Support high resolution metrics [#10925](https://github.com/grafana/grafana/pull/10925), thx [@mtanda](https://github.com/mtanda)
* **Cloudwatch**: Add dimension filtering to CloudWatch `dimension_values()` [#10029](https://github.com/grafana/grafana/issues/10029), thx [@willyhutw](https://github.com/willyhutw) * **Cloudwatch**: Add dimension filtering to CloudWatch `dimension_values()` [#10029](https://github.com/grafana/grafana/issues/10029), thx [@willyhutw](https://github.com/willyhutw)
* **Units**: Second to HH:mm:ss formatter [#11107](https://github.com/grafana/grafana/issues/11107), thx [@gladdiologist](https://github.com/gladdiologist) * **Units**: Second to HH:mm:ss formatter [#11107](https://github.com/grafana/grafana/issues/11107), thx [@gladdiologist](https://github.com/gladdiologist)
* **Singlestat**: Add color to prefix and postfix in singlestat panel [#11143](https://github.com/grafana/grafana/pull/11143), thx [@ApsOps](https://github.com/ApsOps) * **Singlestat**: Add color to prefix and postfix in singlestat panel [#11143](https://github.com/grafana/grafana/pull/11143), thx [@ApsOps](https://github.com/ApsOps)
# 5.0.4 (unreleased)
* **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086)
# 5.0.3 (2018-03-16) # 5.0.3 (2018-03-16)
* **Mysql**: Mysql panic occurring occasionally upon Grafana dashboard access (a bigger patch than the one in 5.0.2) [#11155](https://github.com/grafana/grafana/issues/11155) * **Mysql**: Mysql panic occurring occasionally upon Grafana dashboard access (a bigger patch than the one in 5.0.2) [#11155](https://github.com/grafana/grafana/issues/11155)
Generated
+5 -4
View File
@@ -153,7 +153,6 @@
packages = [ packages = [
".", ".",
"memcache", "memcache",
"mysql",
"postgres", "postgres",
"redis" "redis"
] ]
@@ -179,12 +178,14 @@
[[projects]] [[projects]]
name = "github.com/go-xorm/core" name = "github.com/go-xorm/core"
packages = ["."] packages = ["."]
revision = "e8409d73255791843585964791443dbad877058c" revision = "da1adaf7a28ca792961721a34e6e04945200c890"
version = "v0.5.7"
[[projects]] [[projects]]
name = "github.com/go-xorm/xorm" name = "github.com/go-xorm/xorm"
packages = ["."] packages = ["."]
revision = "6687a2b4e824f4d87f2d65060ec5cb0d896dff1e" revision = "1933dd69e294c0a26c0266637067f24dbb25770c"
version = "v0.6.4"
[[projects]] [[projects]]
branch = "master" branch = "master"
@@ -642,6 +643,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "5e65aeace832f1b4be17e7ff5d5714513c40f31b94b885f64f98f2332968d7c6" inputs-digest = "8a9e651fb8ea49dfd3c6ddc99bd3242b39e453ea9edd11321da79bd2c865e9d1"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1
+2 -4
View File
@@ -85,13 +85,11 @@ ignored = [
[[constraint]] [[constraint]]
name = "github.com/go-xorm/core" name = "github.com/go-xorm/core"
revision = "e8409d73255791843585964791443dbad877058c" version = "0.5.7"
#version = "0.5.7" //keeping this since we would rather depend on version then commit
[[constraint]] [[constraint]]
name = "github.com/go-xorm/xorm" name = "github.com/go-xorm/xorm"
revision = "6687a2b4e824f4d87f2d65060ec5cb0d896dff1e" version = "0.6.4"
#version = "0.6.4" //keeping this since we would rather depend on version then commit
[[constraint]] [[constraint]]
name = "github.com/gorilla/websocket" name = "github.com/gorilla/websocket"
+6
View File
@@ -82,6 +82,9 @@ max_idle_conn = 2
# Max conn setting default is 0 (mean not set) # Max conn setting default is 0 (mean not set)
max_open_conn = max_open_conn =
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
conn_max_lifetime = 14400
# Set to true to log the sql calls and execution times. # Set to true to log the sql calls and execution times.
log_queries = log_queries =
@@ -125,6 +128,9 @@ cookie_secure = false
session_life_time = 86400 session_life_time = 86400
gc_interval_time = 86400 gc_interval_time = 86400
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
conn_max_lifetime = 14400
#################################### Data proxy ########################### #################################### Data proxy ###########################
[dataproxy] [dataproxy]
+3
View File
@@ -90,6 +90,9 @@
# Max conn setting default is 0 (mean not set) # Max conn setting default is 0 (mean not set)
;max_open_conn = ;max_open_conn =
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
;conn_max_lifetime = 14400
# Set to true to log the sql calls and execution times. # Set to true to log the sql calls and execution times.
log_queries = log_queries =
+8 -2
View File
@@ -133,12 +133,18 @@ datasources:
editable: false editable: false
``` ```
#### Extra info per datasource
| Datasource | Misc |
| ---- | ---- |
| Elasticserach | Elasticsearch uses the `database` property to configure the index for a datasource |
#### Json data #### Json data
Since not all datasources have the same configuration settings we only have the most common ones as fields. The rest should be stored as a json blob in the `json_data` field. Here are the most common settings that the core datasources use. Since not all datasources have the same configuration settings we only have the most common ones as fields. The rest should be stored as a json blob in the `json_data` field. Here are the most common settings that the core datasources use.
| Name | Type | Datasource |Description | | Name | Type | Datasource | Description |
| ----| ---- | ---- | --- | | ---- | ---- | ---- | ---- |
| tlsAuth | boolean | *All* | Enable TLS authentication using client cert configured in secure json data | | tlsAuth | boolean | *All* | Enable TLS authentication using client cert configured in secure json data |
| tlsAuthWithCACert | boolean | *All* | Enable TLS authtication using CA cert | | tlsAuthWithCACert | boolean | *All* | Enable TLS authtication using CA cert |
| tlsSkipVerify | boolean | *All* | Controls whether a client verifies the server's certificate chain and host name. | | tlsSkipVerify | boolean | *All* | Controls whether a client verifies the server's certificate chain and host name. |
@@ -234,7 +234,12 @@ The maximum number of connections in the idle connection pool.
### max_open_conn ### max_open_conn
The maximum number of open connections to the database. The maximum number of open connections to the database.
### conn_max_lifetime
Sets the maximum amount of time a connection may be reused. The default is 14400 (which means 14400 seconds or 4 hours). For MySQL, this setting should be shorter than the [`wait_timeout`](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout) variable.
### log_queries ### log_queries
Set to `true` to log the sql calls and execution times. Set to `true` to log the sql calls and execution times.
<hr /> <hr />
+68 -1
View File
@@ -1,6 +1,6 @@
+++ +++
title = "Variables" title = "Variables"
keywords = ["grafana", "templating", "documentation", "guide"] keywords = ["grafana", "templating", "documentation", "guide", "template", "variable"]
type = "docs" type = "docs"
[menu.docs] [menu.docs]
name = "Variables" name = "Variables"
@@ -80,6 +80,73 @@ Option | Description
*Regex* | Regex to filter or capture specific parts of the names return by your data source query. Optional. *Regex* | Regex to filter or capture specific parts of the names return by your data source query. Optional.
*Sort* | Define sort order for options in dropdown. **Disabled** means that the order of options returned by your data source query will be used. *Sort* | Define sort order for options in dropdown. **Disabled** means that the order of options returned by your data source query will be used.
#### Using regex to filter/modify values in the Variable dropdown
Using the Regex Query Option, you filter the list of options returned by the Variable query or modify the options returned.
Examples of filtering on the following list of options:
```text
backend_01
backend_02
backend_03
backend_04
```
##### Filter so that only the options that end with `01` or `02` are returned:
Regex:
```regex
/.*[01|02]/
```
Result:
```text
backend_01
backend_02
```
##### Filter and modify the options using a regex capture group to return part of the text:
Regex:
```regex
/.*(01|02)/
```
Result:
```text
01
02
```
#### Filter and modify - Prometheus Example
List of options:
```text
up{instance="demo.robustperception.io:9090",job="prometheus"} 1 1521630638000
up{instance="demo.robustperception.io:9093",job="alertmanager"} 1 1521630638000
up{instance="demo.robustperception.io:9100",job="node"} 1 1521630638000
```
Regex:
```regex
/.*instance="([^"]*).*/
```
Result:
```text
demo.robustperception.io:9090
demo.robustperception.io:9093
demo.robustperception.io:9100
```
### Query expressions ### Query expressions
The query expressions are different for each data source. The query expressions are different for each data source.
+1 -1
View File
@@ -143,7 +143,7 @@ td[class="stack-column-center"] {
<center> <center>
<p style="text-align: center; font-size: 12px; color: #999999;"> <p style="text-align: center; font-size: 12px; color: #999999;">
Sent by <a href="[[.AppUrl]]">Grafana v[[.BuildVersion]]</a> Sent by <a href="[[.AppUrl]]">Grafana v[[.BuildVersion]]</a>
<br />&copy; 2016 Grafana and raintank <br />&copy; 2018 Grafana Labs
</p> </p>
</center> </center>
</td> </td>
+7 -7
View File
@@ -47,14 +47,14 @@ func AdminCreateUser(c *m.ReqContext, form dtos.AdminCreateUserForm) {
} }
func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordForm) { func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordForm) {
userId := c.ParamsInt64(":id") userID := c.ParamsInt64(":id")
if len(form.Password) < 4 { if len(form.Password) < 4 {
c.JsonApiErr(400, "New password too short", nil) c.JsonApiErr(400, "New password too short", nil)
return return
} }
userQuery := m.GetUserByIdQuery{Id: userId} userQuery := m.GetUserByIdQuery{Id: userID}
if err := bus.Dispatch(&userQuery); err != nil { if err := bus.Dispatch(&userQuery); err != nil {
c.JsonApiErr(500, "Could not read user from database", err) c.JsonApiErr(500, "Could not read user from database", err)
@@ -64,7 +64,7 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt) passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt)
cmd := m.ChangeUserPasswordCommand{ cmd := m.ChangeUserPasswordCommand{
UserId: userId, UserId: userID,
NewPassword: passwordHashed, NewPassword: passwordHashed,
} }
@@ -77,10 +77,10 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
} }
func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermissionsForm) { func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermissionsForm) {
userId := c.ParamsInt64(":id") userID := c.ParamsInt64(":id")
cmd := m.UpdateUserPermissionsCommand{ cmd := m.UpdateUserPermissionsCommand{
UserId: userId, UserId: userID,
IsGrafanaAdmin: form.IsGrafanaAdmin, IsGrafanaAdmin: form.IsGrafanaAdmin,
} }
@@ -93,9 +93,9 @@ func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermis
} }
func AdminDeleteUser(c *m.ReqContext) { func AdminDeleteUser(c *m.ReqContext) {
userId := c.ParamsInt64(":id") userID := c.ParamsInt64(":id")
cmd := m.DeleteUserCommand{UserId: userId} cmd := m.DeleteUserCommand{UserId: userID}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to delete user", err) c.JsonApiErr(500, "Failed to delete user", err)
+3 -3
View File
@@ -26,9 +26,9 @@ func ValidateOrgAlert(c *m.ReqContext) {
} }
func GetAlertStatesForDashboard(c *m.ReqContext) Response { func GetAlertStatesForDashboard(c *m.ReqContext) Response {
dashboardId := c.QueryInt64("dashboardId") dashboardID := c.QueryInt64("dashboardId")
if dashboardId == 0 { if dashboardID == 0 {
return ApiError(400, "Missing query parameter dashboardId", nil) return ApiError(400, "Missing query parameter dashboardId", nil)
} }
@@ -151,7 +151,7 @@ func GetAlertNotifications(c *m.ReqContext) Response {
return Json(200, result) return Json(200, result)
} }
func GetAlertNotificationById(c *m.ReqContext) Response { func GetAlertNotificationByID(c *m.ReqContext) Response {
query := &m.GetAlertNotificationsQuery{ query := &m.GetAlertNotificationsQuery{
OrgId: c.OrgId, OrgId: c.OrgId,
Id: c.ParamsInt64("notificationId"), Id: c.ParamsInt64("notificationId"),
+17 -17
View File
@@ -52,7 +52,7 @@ func (e *CreateAnnotationError) Error() string {
} }
func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response { func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
if canSave, err := canSaveByDashboardId(c, cmd.DashboardId); err != nil || !canSave { if canSave, err := canSaveByDashboardID(c, cmd.DashboardId); err != nil || !canSave {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
@@ -179,18 +179,18 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
} }
func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response { func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
annotationId := c.ParamsInt64(":annotationId") annotationID := c.ParamsInt64(":annotationId")
repo := annotations.GetRepository() repo := annotations.GetRepository()
if resp := canSave(c, repo, annotationId); resp != nil { if resp := canSave(c, repo, annotationID); resp != nil {
return resp return resp
} }
item := annotations.Item{ item := annotations.Item{
OrgId: c.OrgId, OrgId: c.OrgId,
UserId: c.UserId, UserId: c.UserId,
Id: annotationId, Id: annotationID,
Epoch: cmd.Time / 1000, Epoch: cmd.Time / 1000,
Text: cmd.Text, Text: cmd.Text,
Tags: cmd.Tags, Tags: cmd.Tags,
@@ -254,14 +254,14 @@ func DeleteAnnotationById(c *m.ReqContext) Response {
func DeleteAnnotationRegion(c *m.ReqContext) Response { func DeleteAnnotationRegion(c *m.ReqContext) Response {
repo := annotations.GetRepository() repo := annotations.GetRepository()
regionId := c.ParamsInt64(":regionId") regionID := c.ParamsInt64(":regionId")
if resp := canSave(c, repo, regionId); resp != nil { if resp := canSave(c, repo, regionID); resp != nil {
return resp return resp
} }
err := repo.Delete(&annotations.DeleteParams{ err := repo.Delete(&annotations.DeleteParams{
RegionId: regionId, RegionId: regionID,
}) })
if err != nil { if err != nil {
@@ -271,13 +271,13 @@ func DeleteAnnotationRegion(c *m.ReqContext) Response {
return ApiSuccess("Annotation region deleted") return ApiSuccess("Annotation region deleted")
} }
func canSaveByDashboardId(c *m.ReqContext, dashboardId int64) (bool, error) { func canSaveByDashboardID(c *m.ReqContext, dashboardID int64) (bool, error) {
if dashboardId == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) { if dashboardID == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) {
return false, nil return false, nil
} }
if dashboardId > 0 { if dashboardID > 0 {
guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser) guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit { if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
return false, err return false, err
} }
@@ -293,25 +293,25 @@ func canSave(c *m.ReqContext, repo annotations.Repository, annotationId int64) R
return ApiError(500, "Could not find annotation to update", err) return ApiError(500, "Could not find annotation to update", err)
} }
dashboardId := items[0].DashboardId dashboardID := items[0].DashboardId
if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave { if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
return nil return nil
} }
func canSaveByRegionId(c *m.ReqContext, repo annotations.Repository, regionId int64) Response { func canSaveByRegionID(c *m.ReqContext, repo annotations.Repository, regionID int64) Response {
items, err := repo.Find(&annotations.ItemQuery{RegionId: regionId, OrgId: c.OrgId}) items, err := repo.Find(&annotations.ItemQuery{RegionId: regionID, OrgId: c.OrgId})
if err != nil || len(items) == 0 { if err != nil || len(items) == 0 {
return ApiError(500, "Could not find annotation to update", err) return ApiError(500, "Could not find annotation to update", err)
} }
dashboardId := items[0].DashboardId dashboardID := items[0].DashboardId
if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave { if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
+19 -19
View File
@@ -15,7 +15,7 @@ func (hs *HttpServer) registerRoutes() {
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true}) reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN) reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN) reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
redirectFromLegacyDashboardUrl := middleware.RedirectFromLegacyDashboardUrl() redirectFromLegacyDashboardUrl := middleware.RedirectFromLegacyDashboardURL()
redirectFromLegacyDashboardSoloUrl := middleware.RedirectFromLegacyDashboardSoloUrl() redirectFromLegacyDashboardSoloUrl := middleware.RedirectFromLegacyDashboardSoloUrl()
quota := middleware.Quota quota := middleware.Quota
bind := binding.Bind bind := binding.Bind
@@ -110,7 +110,7 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/api/snapshots-delete/:key", reqEditorRole, wrap(DeleteDashboardSnapshot)) r.Get("/api/snapshots-delete/:key", reqEditorRole, wrap(DeleteDashboardSnapshot))
// api renew session based on remember cookie // api renew session based on remember cookie
r.Get("/api/login/ping", quota("session"), LoginApiPing) r.Get("/api/login/ping", quota("session"), LoginAPIPing)
// authed api // authed api
r.Group("/api", func(apiRoute RouteRegister) { r.Group("/api", func(apiRoute RouteRegister) {
@@ -139,7 +139,7 @@ func (hs *HttpServer) registerRoutes() {
apiRoute.Group("/users", func(usersRoute RouteRegister) { apiRoute.Group("/users", func(usersRoute RouteRegister) {
usersRoute.Get("/", wrap(SearchUsers)) usersRoute.Get("/", wrap(SearchUsers))
usersRoute.Get("/search", wrap(SearchUsersWithPaging)) usersRoute.Get("/search", wrap(SearchUsersWithPaging))
usersRoute.Get("/:id", wrap(GetUserById)) usersRoute.Get("/:id", wrap(GetUserByID))
usersRoute.Get("/:id/orgs", wrap(GetUserOrgList)) usersRoute.Get("/:id/orgs", wrap(GetUserOrgList))
// query parameters /users/lookup?loginOrEmail=admin@example.com // query parameters /users/lookup?loginOrEmail=admin@example.com
usersRoute.Get("/lookup", wrap(GetUserByLoginOrEmail)) usersRoute.Get("/lookup", wrap(GetUserByLoginOrEmail))
@@ -149,11 +149,11 @@ func (hs *HttpServer) registerRoutes() {
// team (admin permission required) // team (admin permission required)
apiRoute.Group("/teams", func(teamsRoute RouteRegister) { apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
teamsRoute.Get("/:teamId", wrap(GetTeamById)) teamsRoute.Get("/:teamId", wrap(GetTeamByID))
teamsRoute.Get("/search", wrap(SearchTeams)) teamsRoute.Get("/search", wrap(SearchTeams))
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam)) teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam)) teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
teamsRoute.Delete("/:teamId", wrap(DeleteTeamById)) teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers)) teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers))
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember)) teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember)) teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
@@ -192,10 +192,10 @@ func (hs *HttpServer) registerRoutes() {
// orgs (admin routes) // orgs (admin routes)
apiRoute.Group("/orgs/:orgId", func(orgsRoute RouteRegister) { apiRoute.Group("/orgs/:orgId", func(orgsRoute RouteRegister) {
orgsRoute.Get("/", wrap(GetOrgById)) orgsRoute.Get("/", wrap(GetOrgByID))
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg)) orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress)) orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
orgsRoute.Delete("/", wrap(DeleteOrgById)) orgsRoute.Delete("/", wrap(DeleteOrgByID))
orgsRoute.Get("/users", wrap(GetOrgUsers)) orgsRoute.Get("/users", wrap(GetOrgUsers))
orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser)) orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser)) orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
@@ -211,9 +211,9 @@ func (hs *HttpServer) registerRoutes() {
// auth api keys // auth api keys
apiRoute.Group("/auth/keys", func(keysRoute RouteRegister) { apiRoute.Group("/auth/keys", func(keysRoute RouteRegister) {
keysRoute.Get("/", wrap(GetApiKeys)) keysRoute.Get("/", wrap(GetAPIKeys))
keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey)) keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddAPIKey))
keysRoute.Delete("/:id", wrap(DeleteApiKey)) keysRoute.Delete("/:id", wrap(DeleteAPIKey))
}, reqOrgAdmin) }, reqOrgAdmin)
// Preferences // Preferences
@@ -226,16 +226,16 @@ func (hs *HttpServer) registerRoutes() {
datasourceRoute.Get("/", wrap(GetDataSources)) datasourceRoute.Get("/", wrap(GetDataSources))
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), wrap(AddDataSource)) datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), wrap(AddDataSource))
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource)) datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource))
datasourceRoute.Delete("/:id", wrap(DeleteDataSourceById)) datasourceRoute.Delete("/:id", wrap(DeleteDataSourceByID))
datasourceRoute.Delete("/name/:name", wrap(DeleteDataSourceByName)) datasourceRoute.Delete("/name/:name", wrap(DeleteDataSourceByName))
datasourceRoute.Get("/:id", wrap(GetDataSourceById)) datasourceRoute.Get("/:id", wrap(GetDataSourceByID))
datasourceRoute.Get("/name/:name", wrap(GetDataSourceByName)) datasourceRoute.Get("/name/:name", wrap(GetDataSourceByName))
}, reqOrgAdmin) }, reqOrgAdmin)
apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn) apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIDByName), reqSignedIn)
apiRoute.Get("/plugins", wrap(GetPluginList)) apiRoute.Get("/plugins", wrap(GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById)) apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingByID))
apiRoute.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown)) apiRoute.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown))
apiRoute.Group("/plugins", func(pluginRoute RouteRegister) { apiRoute.Group("/plugins", func(pluginRoute RouteRegister) {
@@ -250,11 +250,11 @@ func (hs *HttpServer) registerRoutes() {
// Folders // Folders
apiRoute.Group("/folders", func(folderRoute RouteRegister) { apiRoute.Group("/folders", func(folderRoute RouteRegister) {
folderRoute.Get("/", wrap(GetFolders)) folderRoute.Get("/", wrap(GetFolders))
folderRoute.Get("/id/:id", wrap(GetFolderById)) folderRoute.Get("/id/:id", wrap(GetFolderByID))
folderRoute.Post("/", bind(m.CreateFolderCommand{}), wrap(CreateFolder)) folderRoute.Post("/", bind(m.CreateFolderCommand{}), wrap(CreateFolder))
folderRoute.Group("/:uid", func(folderUidRoute RouteRegister) { folderRoute.Group("/:uid", func(folderUidRoute RouteRegister) {
folderUidRoute.Get("/", wrap(GetFolderByUid)) folderUidRoute.Get("/", wrap(GetFolderByUID))
folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), wrap(UpdateFolder)) folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), wrap(UpdateFolder))
folderUidRoute.Delete("/", wrap(DeleteFolder)) folderUidRoute.Delete("/", wrap(DeleteFolder))
@@ -268,7 +268,7 @@ func (hs *HttpServer) registerRoutes() {
// Dashboard // Dashboard
apiRoute.Group("/dashboards", func(dashboardRoute RouteRegister) { apiRoute.Group("/dashboards", func(dashboardRoute RouteRegister) {
dashboardRoute.Get("/uid/:uid", wrap(GetDashboard)) dashboardRoute.Get("/uid/:uid", wrap(GetDashboard))
dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUid)) dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUID))
dashboardRoute.Get("/db/:slug", wrap(GetDashboard)) dashboardRoute.Get("/db/:slug", wrap(GetDashboard))
dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard)) dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard))
@@ -314,7 +314,7 @@ func (hs *HttpServer) registerRoutes() {
// metrics // metrics
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics)) apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics))
apiRoute.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios)) apiRoute.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios))
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData)) apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSQLTestData))
apiRoute.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk)) apiRoute.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk))
apiRoute.Group("/alerts", func(alertsRoute RouteRegister) { apiRoute.Group("/alerts", func(alertsRoute RouteRegister) {
@@ -332,7 +332,7 @@ func (hs *HttpServer) registerRoutes() {
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest)) alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest))
alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification)) alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification))
alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification)) alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification))
alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationById)) alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationByID))
alertNotifications.Delete("/:notificationId", wrap(DeleteAlertNotification)) alertNotifications.Delete("/:notificationId", wrap(DeleteAlertNotification))
}, reqEditorRole) }, reqEditorRole)
+3 -3
View File
@@ -7,7 +7,7 @@ import (
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )
func GetApiKeys(c *m.ReqContext) Response { func GetAPIKeys(c *m.ReqContext) Response {
query := m.GetApiKeysQuery{OrgId: c.OrgId} query := m.GetApiKeysQuery{OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
@@ -26,7 +26,7 @@ func GetApiKeys(c *m.ReqContext) Response {
return Json(200, result) return Json(200, result)
} }
func DeleteApiKey(c *m.ReqContext) Response { func DeleteAPIKey(c *m.ReqContext) Response {
id := c.ParamsInt64(":id") id := c.ParamsInt64(":id")
cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId} cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
@@ -39,7 +39,7 @@ func DeleteApiKey(c *m.ReqContext) Response {
return ApiSuccess("API key deleted") return ApiSuccess("API key deleted")
} }
func AddApiKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response { func AddAPIKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
if !cmd.Role.IsValid() { if !cmd.Role.IsValid() {
return ApiError(400, "Invalid role specified", nil) return ApiError(400, "Invalid role specified", nil)
} }
+2 -2
View File
@@ -55,11 +55,11 @@ func InitAppPluginRoutes(r *macaron.Macaron) {
} }
} }
func AppPluginRoute(route *plugins.AppPluginRoute, appId string) macaron.Handler { func AppPluginRoute(route *plugins.AppPluginRoute, appID string) macaron.Handler {
return func(c *m.ReqContext) { return func(c *m.ReqContext) {
path := c.Params("*") path := c.Params("*")
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appId) proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID)
proxy.Transport = pluginProxyTransport proxy.Transport = pluginProxyTransport
proxy.ServeHTTP(c.Resp, c.Req.Request) proxy.ServeHTTP(c.Resp, c.Req.Request)
} }
+1 -1
View File
@@ -99,7 +99,7 @@ func setupScenarioContext(url string) *scenarioContext {
})) }))
sc.m.Use(middleware.GetContextHandler()) sc.m.Use(middleware.GetContextHandler())
sc.m.Use(middleware.Sessioner(&session.Options{})) sc.m.Use(middleware.Sessioner(&session.Options{}, 0))
return sc return sc
} }
+18 -21
View File
@@ -22,12 +22,12 @@ import (
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
func isDashboardStarredByUser(c *m.ReqContext, dashId int64) (bool, error) { func isDashboardStarredByUser(c *m.ReqContext, dashID int64) (bool, error) {
if !c.IsSignedIn { if !c.IsSignedIn {
return false, nil return false, nil
} }
query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashId} query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashID}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return false, err return false, err
} }
@@ -114,24 +114,22 @@ func GetDashboard(c *m.ReqContext) Response {
return Json(200, dto) return Json(200, dto)
} }
func getUserLogin(userId int64) string { func getUserLogin(userID int64) string {
query := m.GetUserByIdQuery{Id: userId} query := m.GetUserByIdQuery{Id: userID}
err := bus.Dispatch(&query) err := bus.Dispatch(&query)
if err != nil { if err != nil {
return "Anonymous" return "Anonymous"
} else {
user := query.Result
return user.Login
} }
return query.Result.Login
} }
func getDashboardHelper(orgId int64, slug string, id int64, uid string) (*m.Dashboard, Response) { func getDashboardHelper(orgID int64, slug string, id int64, uid string) (*m.Dashboard, Response) {
var query m.GetDashboardQuery var query m.GetDashboardQuery
if len(uid) > 0 { if len(uid) > 0 {
query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgId} query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgID}
} else { } else {
query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgId} query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgID}
} }
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
@@ -173,7 +171,7 @@ func DeleteDashboard(c *m.ReqContext) Response {
}) })
} }
func DeleteDashboardByUid(c *m.ReqContext) Response { func DeleteDashboardByUID(c *m.ReqContext) Response {
dash, rsp := getDashboardHelper(c.OrgId, "", 0, c.Params(":uid")) dash, rsp := getDashboardHelper(c.OrgId, "", 0, c.Params(":uid"))
if rsp != nil { if rsp != nil {
return rsp return rsp
@@ -291,9 +289,8 @@ func GetHomeDashboard(c *m.ReqContext) Response {
url := m.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug) url := m.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug)
dashRedirect := dtos.DashboardRedirect{RedirectUri: url} dashRedirect := dtos.DashboardRedirect{RedirectUri: url}
return Json(200, &dashRedirect) return Json(200, &dashRedirect)
} else {
log.Warn("Failed to get slug from database, %s", err.Error())
} }
log.Warn("Failed to get slug from database, %s", err.Error())
} }
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json") filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
@@ -339,22 +336,22 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
// GetDashboardVersions returns all dashboard versions as JSON // GetDashboardVersions returns all dashboard versions as JSON
func GetDashboardVersions(c *m.ReqContext) Response { func GetDashboardVersions(c *m.ReqContext) Response {
dashId := c.ParamsInt64(":dashboardId") dashID := c.ParamsInt64(":dashboardId")
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser) guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave { if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
query := m.GetDashboardVersionsQuery{ query := m.GetDashboardVersionsQuery{
OrgId: c.OrgId, OrgId: c.OrgId,
DashboardId: dashId, DashboardId: dashID,
Limit: c.QueryInt("limit"), Limit: c.QueryInt("limit"),
Start: c.QueryInt("start"), Start: c.QueryInt("start"),
} }
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return ApiError(404, fmt.Sprintf("No versions found for dashboardId %d", dashId), err) return ApiError(404, fmt.Sprintf("No versions found for dashboardId %d", dashID), err)
} }
for _, version := range query.Result { for _, version := range query.Result {
@@ -378,21 +375,21 @@ func GetDashboardVersions(c *m.ReqContext) Response {
// GetDashboardVersion returns the dashboard version with the given ID. // GetDashboardVersion returns the dashboard version with the given ID.
func GetDashboardVersion(c *m.ReqContext) Response { func GetDashboardVersion(c *m.ReqContext) Response {
dashId := c.ParamsInt64(":dashboardId") dashID := c.ParamsInt64(":dashboardId")
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser) guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave { if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
query := m.GetDashboardVersionQuery{ query := m.GetDashboardVersionQuery{
OrgId: c.OrgId, OrgId: c.OrgId,
DashboardId: dashId, DashboardId: dashID,
Version: c.ParamsInt(":id"), Version: c.ParamsInt(":id"),
} }
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return ApiError(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashId), err) return ApiError(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashID), err)
} }
creator := "Anonymous" creator := "Anonymous"
+8 -8
View File
@@ -10,14 +10,14 @@ import (
) )
func GetDashboardPermissionList(c *m.ReqContext) Response { func GetDashboardPermissionList(c *m.ReqContext) Response {
dashId := c.ParamsInt64(":dashboardId") dashID := c.ParamsInt64(":dashboardId")
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "") _, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
if rsp != nil { if rsp != nil {
return rsp return rsp
} }
g := guardian.New(dashId, c.OrgId, c.SignedInUser) g := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
@@ -38,25 +38,25 @@ func GetDashboardPermissionList(c *m.ReqContext) Response {
} }
func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response { func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
dashId := c.ParamsInt64(":dashboardId") dashID := c.ParamsInt64(":dashboardId")
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "") _, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
if rsp != nil { if rsp != nil {
return rsp return rsp
} }
g := guardian.New(dashId, c.OrgId, c.SignedInUser) g := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
cmd := m.UpdateDashboardAclCommand{} cmd := m.UpdateDashboardAclCommand{}
cmd.DashboardId = dashId cmd.DashboardId = dashID
for _, item := range apiCmd.Items { for _, item := range apiCmd.Items {
cmd.Items = append(cmd.Items, &m.DashboardAcl{ cmd.Items = append(cmd.Items, &m.DashboardAcl{
OrgId: c.OrgId, OrgId: c.OrgId,
DashboardId: dashId, DashboardId: dashID,
UserId: item.UserId, UserId: item.UserId,
TeamId: item.TeamId, TeamId: item.TeamId,
Role: item.Role, Role: item.Role,
+2 -2
View File
@@ -106,9 +106,9 @@ func DeleteDashboardSnapshot(c *m.ReqContext) Response {
return ApiError(404, "Failed to get dashboard snapshot", nil) return ApiError(404, "Failed to get dashboard snapshot", nil)
} }
dashboard := query.Result.Dashboard dashboard := query.Result.Dashboard
dashboardId := dashboard.Get("id").MustInt64() dashboardID := dashboard.Get("id").MustInt64()
guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser) guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
canEdit, err := guardian.CanEdit() canEdit, err := guardian.CanEdit()
if err != nil { if err != nil {
return ApiError(500, "Error while checking permissions for snapshot", err) return ApiError(500, "Error while checking permissions for snapshot", err)
+10 -10
View File
@@ -105,7 +105,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403) So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -165,7 +165,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200) So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -271,7 +271,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403) So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -329,7 +329,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403) So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -398,7 +398,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200) So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -468,7 +468,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403) So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -527,7 +527,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200) So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -594,7 +594,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc) CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403) So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() { Convey("Should lookup dashboard by uid", func() {
@@ -837,12 +837,12 @@ func CallDeleteDashboard(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
} }
func CallDeleteDashboardByUid(sc *scenarioContext) { func CallDeleteDashboardByUID(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error { bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
return nil return nil
}) })
sc.handlerFunc = DeleteDashboardByUid sc.handlerFunc = DeleteDashboardByUID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
} }
+4 -4
View File
@@ -13,19 +13,19 @@ import (
const HeaderNameNoBackendCache = "X-Grafana-NoCache" const HeaderNameNoBackendCache = "X-Grafana-NoCache"
func (hs *HttpServer) getDatasourceById(id int64, orgId int64, nocache bool) (*m.DataSource, error) { func (hs *HttpServer) getDatasourceByID(id int64, orgID int64, nocache bool) (*m.DataSource, error) {
cacheKey := fmt.Sprintf("ds-%d", id) cacheKey := fmt.Sprintf("ds-%d", id)
if !nocache { if !nocache {
if cached, found := hs.cache.Get(cacheKey); found { if cached, found := hs.cache.Get(cacheKey); found {
ds := cached.(*m.DataSource) ds := cached.(*m.DataSource)
if ds.OrgId == orgId { if ds.OrgId == orgID {
return ds, nil return ds, nil
} }
} }
} }
query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId} query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgID}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return nil, err return nil, err
} }
@@ -39,7 +39,7 @@ func (hs *HttpServer) ProxyDataSourceRequest(c *m.ReqContext) {
nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true" nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true"
ds, err := hs.getDatasourceById(c.ParamsInt64(":id"), c.OrgId, nocache) ds, err := hs.getDatasourceByID(c.ParamsInt64(":id"), c.OrgId, nocache)
if err != nil { if err != nil {
c.JsonApiErr(500, "Unable to load datasource meta data", err) c.JsonApiErr(500, "Unable to load datasource meta data", err)
+12 -13
View File
@@ -49,7 +49,7 @@ func GetDataSources(c *m.ReqContext) Response {
return Json(200, &result) return Json(200, &result)
} }
func GetDataSourceById(c *m.ReqContext) Response { func GetDataSourceByID(c *m.ReqContext) Response {
query := m.GetDataSourceByIdQuery{ query := m.GetDataSourceByIdQuery{
Id: c.ParamsInt64(":id"), Id: c.ParamsInt64(":id"),
OrgId: c.OrgId, OrgId: c.OrgId,
@@ -68,14 +68,14 @@ func GetDataSourceById(c *m.ReqContext) Response {
return Json(200, &dtos) return Json(200, &dtos)
} }
func DeleteDataSourceById(c *m.ReqContext) Response { func DeleteDataSourceByID(c *m.ReqContext) Response {
id := c.ParamsInt64(":id") id := c.ParamsInt64(":id")
if id <= 0 { if id <= 0 {
return ApiError(400, "Missing valid datasource id", nil) return ApiError(400, "Missing valid datasource id", nil)
} }
ds, err := getRawDataSourceById(id, c.OrgId) ds, err := getRawDataSourceByID(id, c.OrgId)
if err != nil { if err != nil {
return ApiError(400, "Failed to delete datasource", nil) return ApiError(400, "Failed to delete datasource", nil)
} }
@@ -143,7 +143,7 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
cmd.OrgId = c.OrgId cmd.OrgId = c.OrgId
cmd.Id = c.ParamsInt64(":id") cmd.Id = c.ParamsInt64(":id")
err := fillWithSecureJsonData(&cmd) err := fillWithSecureJSONData(&cmd)
if err != nil { if err != nil {
return ApiError(500, "Failed to update datasource", err) return ApiError(500, "Failed to update datasource", err)
} }
@@ -152,9 +152,8 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
if err != nil { if err != nil {
if err == m.ErrDataSourceUpdatingOldVersion { if err == m.ErrDataSourceUpdatingOldVersion {
return ApiError(500, "Failed to update datasource. Reload new version and try again", err) return ApiError(500, "Failed to update datasource. Reload new version and try again", err)
} else {
return ApiError(500, "Failed to update datasource", err)
} }
return ApiError(500, "Failed to update datasource", err)
} }
ds := convertModelToDtos(cmd.Result) ds := convertModelToDtos(cmd.Result)
return Json(200, util.DynMap{ return Json(200, util.DynMap{
@@ -165,12 +164,12 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
}) })
} }
func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error { func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error {
if len(cmd.SecureJsonData) == 0 { if len(cmd.SecureJsonData) == 0 {
return nil return nil
} }
ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId) ds, err := getRawDataSourceByID(cmd.Id, cmd.OrgId)
if err != nil { if err != nil {
return err return err
} }
@@ -179,8 +178,8 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
return m.ErrDatasourceIsReadOnly return m.ErrDatasourceIsReadOnly
} }
secureJsonData := ds.SecureJsonData.Decrypt() secureJSONData := ds.SecureJsonData.Decrypt()
for k, v := range secureJsonData { for k, v := range secureJSONData {
if _, ok := cmd.SecureJsonData[k]; !ok { if _, ok := cmd.SecureJsonData[k]; !ok {
cmd.SecureJsonData[k] = v cmd.SecureJsonData[k] = v
@@ -190,10 +189,10 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
return nil return nil
} }
func getRawDataSourceById(id int64, orgId int64) (*m.DataSource, error) { func getRawDataSourceByID(id int64, orgID int64) (*m.DataSource, error) {
query := m.GetDataSourceByIdQuery{ query := m.GetDataSourceByIdQuery{
Id: id, Id: id,
OrgId: orgId, OrgId: orgID,
} }
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
@@ -220,7 +219,7 @@ func GetDataSourceByName(c *m.ReqContext) Response {
} }
// Get /api/datasources/id/:name // Get /api/datasources/id/:name
func GetDataSourceIdByName(c *m.ReqContext) Response { func GetDataSourceIDByName(c *m.ReqContext) Response {
query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId} query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
+2 -2
View File
@@ -31,7 +31,7 @@ func GetFolders(c *m.ReqContext) Response {
return Json(200, result) return Json(200, result)
} }
func GetFolderByUid(c *m.ReqContext) Response { func GetFolderByUID(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
folder, err := s.GetFolderByUid(c.Params(":uid")) folder, err := s.GetFolderByUid(c.Params(":uid"))
@@ -43,7 +43,7 @@ func GetFolderByUid(c *m.ReqContext) Response {
return Json(200, toFolderDto(g, folder)) return Json(200, toFolderDto(g, folder))
} }
func GetFolderById(c *m.ReqContext) Response { func GetFolderByID(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
folder, err := s.GetFolderById(c.ParamsInt64(":id")) folder, err := s.GetFolderById(c.ParamsInt64(":id"))
if err != nil { if err != nil {
+2 -2
View File
@@ -133,8 +133,8 @@ func TestFoldersApiEndpoint(t *testing.T) {
}) })
} }
func callGetFolderByUid(sc *scenarioContext) { func callGetFolderByUID(sc *scenarioContext) {
sc.handlerFunc = GetFolderByUid sc.handlerFunc = GetFolderByUID
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
} }
+2 -2
View File
@@ -39,7 +39,7 @@ type HttpServer struct {
httpSrv *http.Server httpSrv *http.Server
} }
func NewHttpServer() *HttpServer { func NewHTTPServer() *HttpServer {
return &HttpServer{ return &HttpServer{
log: log.New("http.server"), log: log.New("http.server"),
cache: gocache.New(5*time.Minute, 10*time.Minute), cache: gocache.New(5*time.Minute, 10*time.Minute),
@@ -175,7 +175,7 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
m.Use(hs.healthHandler) m.Use(hs.healthHandler)
m.Use(hs.metricsEndpoint) m.Use(hs.metricsEndpoint)
m.Use(middleware.GetContextHandler()) m.Use(middleware.GetContextHandler())
m.Use(middleware.Sessioner(&setting.SessionOptions)) m.Use(middleware.Sessioner(&setting.SessionOptions, setting.SessionConnMaxLifetime))
m.Use(middleware.OrgRedirect()) m.Use(middleware.OrgRedirect())
// needs to be after context handler // needs to be after context handler
+15 -14
View File
@@ -32,13 +32,13 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
locale = parts[0] locale = parts[0]
} }
appUrl := setting.AppUrl appURL := setting.AppUrl
appSubUrl := setting.AppSubUrl appSubURL := setting.AppSubUrl
// special case when doing localhost call from phantomjs // special case when doing localhost call from phantomjs
if c.IsRenderCall { if c.IsRenderCall {
appUrl = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort) appURL = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
appSubUrl = "" appSubURL = ""
settings["appSubUrl"] = "" settings["appSubUrl"] = ""
} }
@@ -62,8 +62,8 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
}, },
Settings: settings, Settings: settings,
Theme: prefs.Theme, Theme: prefs.Theme,
AppUrl: appUrl, AppUrl: appURL,
AppSubUrl: appSubUrl, AppSubUrl: appSubURL,
GoogleAnalyticsId: setting.GoogleAnalyticsId, GoogleAnalyticsId: setting.GoogleAnalyticsId,
GoogleTagManagerId: setting.GoogleTagManagerId, GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion, BuildVersion: setting.BuildVersion,
@@ -80,8 +80,8 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
data.User.Name = data.User.Login data.User.Name = data.User.Login
} }
themeUrlParam := c.Query("theme") themeURLParam := c.Query("theme")
if themeUrlParam == "light" { if themeURLParam == "light" {
data.User.LightTheme = true data.User.LightTheme = true
data.Theme = "light" data.Theme = "light"
} }
@@ -299,12 +299,12 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
} }
func Index(c *m.ReqContext) { func Index(c *m.ReqContext) {
if data, err := setIndexViewData(c); err != nil { data, err := setIndexViewData(c)
if err != nil {
c.Handle(500, "Failed to get settings", err) c.Handle(500, "Failed to get settings", err)
return return
} else {
c.HTML(200, "index", data)
} }
c.HTML(200, "index", data)
} }
func NotFoundHandler(c *m.ReqContext) { func NotFoundHandler(c *m.ReqContext) {
@@ -313,10 +313,11 @@ func NotFoundHandler(c *m.ReqContext) {
return return
} }
if data, err := setIndexViewData(c); err != nil { data, err := setIndexViewData(c)
if err != nil {
c.Handle(500, "Failed to get settings", err) c.Handle(500, "Failed to get settings", err)
return return
} else {
c.HTML(404, "index", data)
} }
c.HTML(404, "index", data)
} }
+3 -3
View File
@@ -14,7 +14,7 @@ import (
) )
const ( const (
VIEW_INDEX = "index" ViewIndex = "index"
) )
func LoginView(c *m.ReqContext) { func LoginView(c *m.ReqContext) {
@@ -40,7 +40,7 @@ func LoginView(c *m.ReqContext) {
} }
if !tryLoginUsingRememberCookie(c) { if !tryLoginUsingRememberCookie(c) {
c.HTML(200, VIEW_INDEX, viewData) c.HTML(200, ViewIndex, viewData)
return return
} }
@@ -87,7 +87,7 @@ func tryLoginUsingRememberCookie(c *m.ReqContext) bool {
return true return true
} }
func LoginApiPing(c *m.ReqContext) { func LoginAPIPing(c *m.ReqContext) {
if !tryLoginUsingRememberCookie(c) { if !tryLoginUsingRememberCookie(c) {
c.JsonApiErr(401, "Unauthorized", nil) c.JsonApiErr(401, "Unauthorized", nil)
return return
+3 -3
View File
@@ -20,12 +20,12 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
return ApiError(400, "No queries found in query", nil) return ApiError(400, "No queries found in query", nil)
} }
dsId, err := reqDto.Queries[0].Get("datasourceId").Int64() dsID, err := reqDto.Queries[0].Get("datasourceId").Int64()
if err != nil { if err != nil {
return ApiError(400, "Query missing datasourceId", nil) return ApiError(400, "Query missing datasourceId", nil)
} }
dsQuery := m.GetDataSourceByIdQuery{Id: dsId, OrgId: c.OrgId} dsQuery := m.GetDataSourceByIdQuery{Id: dsID, OrgId: c.OrgId}
if err := bus.Dispatch(&dsQuery); err != nil { if err := bus.Dispatch(&dsQuery); err != nil {
return ApiError(500, "failed to fetch data source", err) return ApiError(500, "failed to fetch data source", err)
} }
@@ -82,7 +82,7 @@ func GenerateError(c *m.ReqContext) Response {
} }
// GET /api/tsdb/testdata/gensql // GET /api/tsdb/testdata/gensql
func GenerateSqlTestData(c *m.ReqContext) Response { func GenerateSQLTestData(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.InsertSqlTestDataCommand{}); err != nil { if err := bus.Dispatch(&m.InsertSqlTestDataCommand{}); err != nil {
return ApiError(500, "Failed to insert test data", err) return ApiError(500, "Failed to insert test data", err)
} }
+6 -6
View File
@@ -15,7 +15,7 @@ func GetOrgCurrent(c *m.ReqContext) Response {
} }
// GET /api/orgs/:orgId // GET /api/orgs/:orgId
func GetOrgById(c *m.ReqContext) Response { func GetOrgByID(c *m.ReqContext) Response {
return getOrgHelper(c.ParamsInt64(":orgId")) return getOrgHelper(c.ParamsInt64(":orgId"))
} }
@@ -106,8 +106,8 @@ func UpdateOrg(c *m.ReqContext, form dtos.UpdateOrgForm) Response {
return updateOrgHelper(form, c.ParamsInt64(":orgId")) return updateOrgHelper(form, c.ParamsInt64(":orgId"))
} }
func updateOrgHelper(form dtos.UpdateOrgForm, orgId int64) Response { func updateOrgHelper(form dtos.UpdateOrgForm, orgID int64) Response {
cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgId} cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgNameTaken { if err == m.ErrOrgNameTaken {
return ApiError(400, "Organization name taken", err) return ApiError(400, "Organization name taken", err)
@@ -128,9 +128,9 @@ func UpdateOrgAddress(c *m.ReqContext, form dtos.UpdateOrgAddressForm) Response
return updateOrgAddressHelper(form, c.ParamsInt64(":orgId")) return updateOrgAddressHelper(form, c.ParamsInt64(":orgId"))
} }
func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Response { func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgID int64) Response {
cmd := m.UpdateOrgAddressCommand{ cmd := m.UpdateOrgAddressCommand{
OrgId: orgId, OrgId: orgID,
Address: m.Address{ Address: m.Address{
Address1: form.Address1, Address1: form.Address1,
Address2: form.Address2, Address2: form.Address2,
@@ -149,7 +149,7 @@ func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Respons
} }
// GET /api/orgs/:orgId // GET /api/orgs/:orgId
func DeleteOrgById(c *m.ReqContext) Response { func DeleteOrgByID(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.DeleteOrgCommand{Id: c.ParamsInt64(":orgId")}); err != nil { if err := bus.Dispatch(&m.DeleteOrgCommand{Id: c.ParamsInt64(":orgId")}); err != nil {
if err == m.ErrOrgNotFound { if err == m.ErrOrgNotFound {
return ApiError(404, "Failed to delete organization. ID not found", nil) return ApiError(404, "Failed to delete organization. ID not found", nil)
+15 -16
View File
@@ -96,26 +96,25 @@ func inviteExistingUserToOrg(c *m.ReqContext, user *m.User, inviteDto *dtos.AddI
return ApiError(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err) return ApiError(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err)
} }
return ApiError(500, "Error while trying to create org user", err) return ApiError(500, "Error while trying to create org user", err)
} else { }
if inviteDto.SendEmail && util.IsEmail(user.Email) { if inviteDto.SendEmail && util.IsEmail(user.Email) {
emailCmd := m.SendEmailCommand{ emailCmd := m.SendEmailCommand{
To: []string{user.Email}, To: []string{user.Email},
Template: "invited_to_org.html", Template: "invited_to_org.html",
Data: map[string]interface{}{ Data: map[string]interface{}{
"Name": user.NameOrFallback(), "Name": user.NameOrFallback(),
"OrgName": c.OrgName, "OrgName": c.OrgName,
"InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login), "InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
}, },
}
if err := bus.Dispatch(&emailCmd); err != nil {
return ApiError(500, "Failed to send email invited_to_org", err)
}
} }
return ApiSuccess(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName)) if err := bus.Dispatch(&emailCmd); err != nil {
return ApiError(500, "Failed to send email invited_to_org", err)
}
} }
return ApiSuccess(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
} }
func RevokeInvite(c *m.ReqContext) Response { func RevokeInvite(c *m.ReqContext) Response {
+9 -9
View File
@@ -53,9 +53,9 @@ func GetOrgUsers(c *m.ReqContext) Response {
return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0) return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
} }
func getOrgUsersHelper(orgId int64, query string, limit int) Response { func getOrgUsersHelper(orgID int64, query string, limit int) Response {
q := m.GetOrgUsersQuery{ q := m.GetOrgUsersQuery{
OrgId: orgId, OrgId: orgID,
Query: query, Query: query,
Limit: limit, Limit: limit,
} }
@@ -102,19 +102,19 @@ func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
// DELETE /api/org/users/:userId // DELETE /api/org/users/:userId
func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response { func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
userId := c.ParamsInt64(":userId") userID := c.ParamsInt64(":userId")
return removeOrgUserHelper(c.OrgId, userId) return removeOrgUserHelper(c.OrgId, userID)
} }
// DELETE /api/orgs/:orgId/users/:userId // DELETE /api/orgs/:orgId/users/:userId
func RemoveOrgUser(c *m.ReqContext) Response { func RemoveOrgUser(c *m.ReqContext) Response {
userId := c.ParamsInt64(":userId") userID := c.ParamsInt64(":userId")
orgId := c.ParamsInt64(":orgId") orgID := c.ParamsInt64(":orgId")
return removeOrgUserHelper(orgId, userId) return removeOrgUserHelper(orgID, userID)
} }
func removeOrgUserHelper(orgId int64, userId int64) Response { func removeOrgUserHelper(orgID int64, userID int64) Response {
cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: userId} cmd := m.RemoveOrgUserCommand{OrgId: orgID, UserId: userID}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrLastOrgAdmin { if err == m.ErrLastOrgAdmin {
+2 -2
View File
@@ -127,9 +127,9 @@ func GetPlaylistItems(c *m.ReqContext) Response {
} }
func GetPlaylistDashboards(c *m.ReqContext) Response { func GetPlaylistDashboards(c *m.ReqContext) Response {
playlistId := c.ParamsInt64(":id") playlistID := c.ParamsInt64(":id")
playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistId) playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistID)
if err != nil { if err != nil {
return ApiError(500, "Could not load dashboards", err) return ApiError(500, "Could not load dashboards", err)
} }
+27 -29
View File
@@ -34,29 +34,27 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
return result, nil return result, nil
} }
func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice { func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
result := make(dtos.PlaylistDashboardsSlice, 0) result := make(dtos.PlaylistDashboardsSlice, 0)
if len(dashboardByTag) > 0 { for _, tag := range dashboardByTag {
for _, tag := range dashboardByTag { searchQuery := search.Query{
searchQuery := search.Query{ Title: "",
Title: "", Tags: []string{tag},
Tags: []string{tag}, SignedInUser: signedInUser,
SignedInUser: signedInUser, Limit: 100,
Limit: 100, IsStarred: false,
IsStarred: false, OrgId: orgID,
OrgId: orgId, }
}
if err := bus.Dispatch(&searchQuery); err == nil { if err := bus.Dispatch(&searchQuery); err == nil {
for _, item := range searchQuery.Result { for _, item := range searchQuery.Result {
result = append(result, dtos.PlaylistDashboard{ result = append(result, dtos.PlaylistDashboard{
Id: item.Id, Id: item.Id,
Title: item.Title, Title: item.Title,
Uri: item.Uri, Uri: item.Uri,
Order: dashboardTagOrder[tag], Order: dashboardTagOrder[tag],
}) })
}
} }
} }
} }
@@ -64,19 +62,19 @@ func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboar
return result return result
} }
func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistId int64) (dtos.PlaylistDashboardsSlice, error) { func LoadPlaylistDashboards(orgID int64, signedInUser *m.SignedInUser, playlistID int64) (dtos.PlaylistDashboardsSlice, error) {
playlistItems, _ := LoadPlaylistItems(playlistId) playlistItems, _ := LoadPlaylistItems(playlistID)
dashboardByIds := make([]int64, 0) dashboardByIDs := make([]int64, 0)
dashboardByTag := make([]string, 0) dashboardByTag := make([]string, 0)
dashboardIdOrder := make(map[int64]int) dashboardIDOrder := make(map[int64]int)
dashboardTagOrder := make(map[string]int) dashboardTagOrder := make(map[string]int)
for _, i := range playlistItems { for _, i := range playlistItems {
if i.Type == "dashboard_by_id" { if i.Type == "dashboard_by_id" {
dashboardId, _ := strconv.ParseInt(i.Value, 10, 64) dashboardID, _ := strconv.ParseInt(i.Value, 10, 64)
dashboardByIds = append(dashboardByIds, dashboardId) dashboardByIDs = append(dashboardByIDs, dashboardID)
dashboardIdOrder[dashboardId] = i.Order dashboardIDOrder[dashboardID] = i.Order
} }
if i.Type == "dashboard_by_tag" { if i.Type == "dashboard_by_tag" {
@@ -87,9 +85,9 @@ func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistI
result := make(dtos.PlaylistDashboardsSlice, 0) result := make(dtos.PlaylistDashboardsSlice, 0)
var k, _ = populateDashboardsById(dashboardByIds, dashboardIdOrder) var k, _ = populateDashboardsById(dashboardByIDs, dashboardIDOrder)
result = append(result, k...) result = append(result, k...)
result = append(result, populateDashboardsByTag(orgId, signedInUser, dashboardByTag, dashboardTagOrder)...) result = append(result, populateDashboardsByTag(orgID, signedInUser, dashboardByTag, dashboardTagOrder)...)
sort.Sort(result) sort.Sort(result)
return result, nil return result, nil
+46 -44
View File
@@ -78,48 +78,48 @@ func GetPluginList(c *m.ReqContext) Response {
return Json(200, result) return Json(200, result)
} }
func GetPluginSettingById(c *m.ReqContext) Response { func GetPluginSettingByID(c *m.ReqContext) Response {
pluginId := c.Params(":pluginId") pluginID := c.Params(":pluginId")
if def, exists := plugins.Plugins[pluginId]; !exists { def, exists := plugins.Plugins[pluginID]
if !exists {
return ApiError(404, "Plugin not found, no installed plugin with that id", nil) return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
} else {
dto := &dtos.PluginSetting{
Type: def.Type,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
LatestVersion: def.GrafanaNetVersion,
HasUpdate: def.GrafanaNetHasUpdate,
State: def.State,
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err != m.ErrPluginSettingNotFound {
return ApiError(500, "Failed to get login settings", nil)
}
} else {
dto.Enabled = query.Result.Enabled
dto.Pinned = query.Result.Pinned
dto.JsonData = query.Result.JsonData
}
return Json(200, dto)
} }
dto := &dtos.PluginSetting{
Type: def.Type,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
LatestVersion: def.GrafanaNetVersion,
HasUpdate: def.GrafanaNetHasUpdate,
State: def.State,
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginID, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err != m.ErrPluginSettingNotFound {
return ApiError(500, "Failed to get login settings", nil)
}
} else {
dto.Enabled = query.Result.Enabled
dto.Pinned = query.Result.Pinned
dto.JsonData = query.Result.JsonData
}
return Json(200, dto)
} }
func UpdatePluginSetting(c *m.ReqContext, cmd m.UpdatePluginSettingCmd) Response { func UpdatePluginSetting(c *m.ReqContext, cmd m.UpdatePluginSettingCmd) Response {
pluginId := c.Params(":pluginId") pluginID := c.Params(":pluginId")
cmd.OrgId = c.OrgId cmd.OrgId = c.OrgId
cmd.PluginId = pluginId cmd.PluginId = pluginID
if _, ok := plugins.Apps[cmd.PluginId]; !ok { if _, ok := plugins.Apps[cmd.PluginId]; !ok {
return ApiError(404, "Plugin not installed.", nil) return ApiError(404, "Plugin not installed.", nil)
@@ -133,34 +133,36 @@ func UpdatePluginSetting(c *m.ReqContext, cmd m.UpdatePluginSettingCmd) Response
} }
func GetPluginDashboards(c *m.ReqContext) Response { func GetPluginDashboards(c *m.ReqContext) Response {
pluginId := c.Params(":pluginId") pluginID := c.Params(":pluginId")
if list, err := plugins.GetPluginDashboards(c.OrgId, pluginId); err != nil { list, err := plugins.GetPluginDashboards(c.OrgId, pluginID)
if err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok { if notfound, ok := err.(plugins.PluginNotFoundError); ok {
return ApiError(404, notfound.Error(), nil) return ApiError(404, notfound.Error(), nil)
} }
return ApiError(500, "Failed to get plugin dashboards", err) return ApiError(500, "Failed to get plugin dashboards", err)
} else {
return Json(200, list)
} }
return Json(200, list)
} }
func GetPluginMarkdown(c *m.ReqContext) Response { func GetPluginMarkdown(c *m.ReqContext) Response {
pluginId := c.Params(":pluginId") pluginID := c.Params(":pluginId")
name := c.Params(":name") name := c.Params(":name")
if content, err := plugins.GetPluginMarkdown(pluginId, name); err != nil { content, err := plugins.GetPluginMarkdown(pluginID, name)
if err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok { if notfound, ok := err.(plugins.PluginNotFoundError); ok {
return ApiError(404, notfound.Error(), nil) return ApiError(404, notfound.Error(), nil)
} }
return ApiError(500, "Could not get markdown file", err) return ApiError(500, "Could not get markdown file", err)
} else {
resp := Respond(200, content)
resp.Header("Content-Type", "text/plain; charset=utf-8")
return resp
} }
resp := Respond(200, content)
resp.Header("Content-Type", "text/plain; charset=utf-8")
return resp
} }
func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Response { func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Response {
+5 -5
View File
@@ -24,8 +24,8 @@ func GetUserPreferences(c *m.ReqContext) Response {
return getPreferencesFor(c.OrgId, c.UserId) return getPreferencesFor(c.OrgId, c.UserId)
} }
func getPreferencesFor(orgId int64, userId int64) Response { func getPreferencesFor(orgID int64, userID int64) Response {
prefsQuery := m.GetPreferencesQuery{UserId: userId, OrgId: orgId} prefsQuery := m.GetPreferencesQuery{UserId: userID, OrgId: orgID}
if err := bus.Dispatch(&prefsQuery); err != nil { if err := bus.Dispatch(&prefsQuery); err != nil {
return ApiError(500, "Failed to get preferences", err) return ApiError(500, "Failed to get preferences", err)
@@ -45,10 +45,10 @@ func UpdateUserPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response
return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd) return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd)
} }
func updatePreferencesFor(orgId int64, userId int64, dtoCmd *dtos.UpdatePrefsCmd) Response { func updatePreferencesFor(orgID int64, userID int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
saveCmd := m.SavePreferencesCommand{ saveCmd := m.SavePreferencesCommand{
UserId: userId, UserId: userID,
OrgId: orgId, OrgId: orgID,
Theme: dtoCmd.Theme, Theme: dtoCmd.Theme,
Timezone: dtoCmd.Timezone, Timezone: dtoCmd.Timezone,
HomeDashboardId: dtoCmd.HomeDashboardId, HomeDashboardId: dtoCmd.HomeDashboardId,
+8 -8
View File
@@ -25,19 +25,19 @@ func Search(c *m.ReqContext) {
permission = m.PERMISSION_EDIT permission = m.PERMISSION_EDIT
} }
dbids := make([]int64, 0) dbIDs := make([]int64, 0)
for _, id := range c.QueryStrings("dashboardIds") { for _, id := range c.QueryStrings("dashboardIds") {
dashboardId, err := strconv.ParseInt(id, 10, 64) dashboardID, err := strconv.ParseInt(id, 10, 64)
if err == nil { if err == nil {
dbids = append(dbids, dashboardId) dbIDs = append(dbIDs, dashboardID)
} }
} }
folderIds := make([]int64, 0) folderIDs := make([]int64, 0)
for _, id := range c.QueryStrings("folderIds") { for _, id := range c.QueryStrings("folderIds") {
folderId, err := strconv.ParseInt(id, 10, 64) folderID, err := strconv.ParseInt(id, 10, 64)
if err == nil { if err == nil {
folderIds = append(folderIds, folderId) folderIDs = append(folderIDs, folderID)
} }
} }
@@ -48,9 +48,9 @@ func Search(c *m.ReqContext) {
Limit: limit, Limit: limit,
IsStarred: starred == "true", IsStarred: starred == "true",
OrgId: c.OrgId, OrgId: c.OrgId,
DashboardIds: dbids, DashboardIds: dbIDs,
Type: dashboardType, Type: dashboardType,
FolderIds: folderIds, FolderIds: folderIDs,
Permission: permission, Permission: permission,
} }
+2 -2
View File
@@ -38,7 +38,7 @@ func UpdateTeam(c *m.ReqContext, cmd m.UpdateTeamCommand) Response {
} }
// DELETE /api/teams/:teamId // DELETE /api/teams/:teamId
func DeleteTeamById(c *m.ReqContext) Response { func DeleteTeamByID(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.DeleteTeamCommand{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}); err != nil { if err := bus.Dispatch(&m.DeleteTeamCommand{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}); err != nil {
if err == m.ErrTeamNotFound { if err == m.ErrTeamNotFound {
return ApiError(404, "Failed to delete Team. ID not found", nil) return ApiError(404, "Failed to delete Team. ID not found", nil)
@@ -82,7 +82,7 @@ func SearchTeams(c *m.ReqContext) Response {
} }
// GET /api/teams/:teamId // GET /api/teams/:teamId
func GetTeamById(c *m.ReqContext) Response { func GetTeamByID(c *m.ReqContext) Response {
query := m.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")} query := m.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
+18 -18
View File
@@ -14,12 +14,12 @@ func GetSignedInUser(c *m.ReqContext) Response {
} }
// GET /api/users/:id // GET /api/users/:id
func GetUserById(c *m.ReqContext) Response { func GetUserByID(c *m.ReqContext) Response {
return getUserUserProfile(c.ParamsInt64(":id")) return getUserUserProfile(c.ParamsInt64(":id"))
} }
func getUserUserProfile(userId int64) Response { func getUserUserProfile(userID int64) Response {
query := m.GetUserProfileQuery{UserId: userId} query := m.GetUserProfileQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
if err == m.ErrUserNotFound { if err == m.ErrUserNotFound {
@@ -75,14 +75,14 @@ func UpdateUser(c *m.ReqContext, cmd m.UpdateUserCommand) Response {
//POST /api/users/:id/using/:orgId //POST /api/users/:id/using/:orgId
func UpdateUserActiveOrg(c *m.ReqContext) Response { func UpdateUserActiveOrg(c *m.ReqContext) Response {
userId := c.ParamsInt64(":id") userID := c.ParamsInt64(":id")
orgId := c.ParamsInt64(":orgId") orgID := c.ParamsInt64(":orgId")
if !validateUsingOrg(userId, orgId) { if !validateUsingOrg(userID, orgID) {
return ApiError(401, "Not a valid organization", nil) return ApiError(401, "Not a valid organization", nil)
} }
cmd := m.SetUsingOrgCommand{UserId: userId, OrgId: orgId} cmd := m.SetUsingOrgCommand{UserId: userID, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change active organization", err) return ApiError(500, "Failed to change active organization", err)
@@ -116,8 +116,8 @@ func GetUserOrgList(c *m.ReqContext) Response {
return getUserOrgList(c.ParamsInt64(":id")) return getUserOrgList(c.ParamsInt64(":id"))
} }
func getUserOrgList(userId int64) Response { func getUserOrgList(userID int64) Response {
query := m.GetUserOrgListQuery{UserId: userId} query := m.GetUserOrgListQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get user organizations", err) return ApiError(500, "Failed to get user organizations", err)
@@ -126,8 +126,8 @@ func getUserOrgList(userId int64) Response {
return Json(200, query.Result) return Json(200, query.Result)
} }
func validateUsingOrg(userId int64, orgId int64) bool { func validateUsingOrg(userID int64, orgID int64) bool {
query := m.GetUserOrgListQuery{UserId: userId} query := m.GetUserOrgListQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return false return false
@@ -136,7 +136,7 @@ func validateUsingOrg(userId int64, orgId int64) bool {
// validate that the org id in the list // validate that the org id in the list
valid := false valid := false
for _, other := range query.Result { for _, other := range query.Result {
if other.OrgId == orgId { if other.OrgId == orgID {
valid = true valid = true
} }
} }
@@ -146,13 +146,13 @@ func validateUsingOrg(userId int64, orgId int64) bool {
// POST /api/user/using/:id // POST /api/user/using/:id
func UserSetUsingOrg(c *m.ReqContext) Response { func UserSetUsingOrg(c *m.ReqContext) Response {
orgId := c.ParamsInt64(":id") orgID := c.ParamsInt64(":id")
if !validateUsingOrg(c.UserId, orgId) { if !validateUsingOrg(c.UserId, orgID) {
return ApiError(401, "Not a valid organization", nil) return ApiError(401, "Not a valid organization", nil)
} }
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId} cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change active organization", err) return ApiError(500, "Failed to change active organization", err)
@@ -163,13 +163,13 @@ func UserSetUsingOrg(c *m.ReqContext) Response {
// GET /profile/switch-org/:id // GET /profile/switch-org/:id
func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) { func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
orgId := c.ParamsInt64(":id") orgID := c.ParamsInt64(":id")
if !validateUsingOrg(c.UserId, orgId) { if !validateUsingOrg(c.UserId, orgID) {
NotFoundHandler(c) NotFoundHandler(c)
} }
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId} cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
NotFoundHandler(c) NotFoundHandler(c)
+1 -1
View File
@@ -120,7 +120,7 @@ func (g *GrafanaServerImpl) initLogging() {
} }
func (g *GrafanaServerImpl) startHttpServer() error { func (g *GrafanaServerImpl) startHttpServer() error {
g.httpServer = api.NewHttpServer() g.httpServer = api.NewHTTPServer()
err := g.httpServer.Start(g.context) err := g.httpServer.Start(g.context)
+3 -3
View File
@@ -17,10 +17,10 @@ type AuthOptions struct {
} }
func getRequestUserId(c *m.ReqContext) int64 { func getRequestUserId(c *m.ReqContext) int64 {
userId := c.Session.Get(session.SESS_KEY_USERID) userID := c.Session.Get(session.SESS_KEY_USERID)
if userId != nil { if userID != nil {
return userId.(int64) return userID.(int64)
} }
return 0 return 0
+1 -1
View File
@@ -20,7 +20,7 @@ func getDashboardUrlBySlug(orgId int64, slug string) (string, error) {
return m.GetDashboardUrl(query.Result.Uid, query.Result.Slug), nil return m.GetDashboardUrl(query.Result.Uid, query.Result.Slug), nil
} }
func RedirectFromLegacyDashboardUrl() macaron.Handler { func RedirectFromLegacyDashboardURL() macaron.Handler {
return func(c *m.ReqContext) { return func(c *m.ReqContext) {
slug := c.Params("slug") slug := c.Params("slug")
+9 -9
View File
@@ -13,7 +13,7 @@ import (
func TestMiddlewareDashboardRedirect(t *testing.T) { func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Given the dashboard redirect middleware", t, func() { Convey("Given the dashboard redirect middleware", t, func() {
bus.ClearBusHandlers() bus.ClearBusHandlers()
redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardUrl() redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardURL()
redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloUrl() redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloUrl()
fakeDash := m.NewDashboard("Child dash") fakeDash := m.NewDashboard("Child dash")
@@ -34,9 +34,9 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() { Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301) So(sc.resp.Code, ShouldEqual, 301)
redirectUrl, _ := sc.resp.Result().Location() redirectURL, _ := sc.resp.Result().Location()
So(redirectUrl.Path, ShouldEqual, m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)) So(redirectURL.Path, ShouldEqual, m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug))
So(len(redirectUrl.Query()), ShouldEqual, 2) So(len(redirectURL.Query()), ShouldEqual, 2)
}) })
}) })
@@ -47,11 +47,11 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() { Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301) So(sc.resp.Code, ShouldEqual, 301)
redirectUrl, _ := sc.resp.Result().Location() redirectURL, _ := sc.resp.Result().Location()
expectedUrl := m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug) expectedURL := m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
expectedUrl = strings.Replace(expectedUrl, "/d/", "/d-solo/", 1) expectedURL = strings.Replace(expectedURL, "/d/", "/d-solo/", 1)
So(redirectUrl.Path, ShouldEqual, expectedUrl) So(redirectURL.Path, ShouldEqual, expectedURL)
So(len(redirectUrl.Query()), ShouldEqual, 2) So(len(redirectURL.Query()), ShouldEqual, 2)
}) })
}) })
}) })
+1 -1
View File
@@ -338,7 +338,7 @@ func middlewareScenario(desc string, fn scenarioFunc) {
sc.m.Use(GetContextHandler()) sc.m.Use(GetContextHandler())
// mock out gc goroutine // mock out gc goroutine
session.StartSessionGC = func() {} session.StartSessionGC = func() {}
sc.m.Use(Sessioner(&ms.Options{})) sc.m.Use(Sessioner(&ms.Options{}, 0))
sc.m.Use(OrgRedirect()) sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders()) sc.m.Use(AddDefaultResponseHeaders())
+7 -7
View File
@@ -14,10 +14,10 @@ import (
func TestRecoveryMiddleware(t *testing.T) { func TestRecoveryMiddleware(t *testing.T) {
Convey("Given an api route that panics", t, func() { Convey("Given an api route that panics", t, func() {
apiUrl := "/api/whatever" apiURL := "/api/whatever"
recoveryScenario("recovery middleware should return json", apiUrl, func(sc *scenarioContext) { recoveryScenario("recovery middleware should return json", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler sc.handlerFunc = PanicHandler
sc.fakeReq("GET", apiUrl).exec() sc.fakeReq("GET", apiURL).exec()
sc.req.Header.Add("content-type", "application/json") sc.req.Header.Add("content-type", "application/json")
So(sc.resp.Code, ShouldEqual, 500) So(sc.resp.Code, ShouldEqual, 500)
@@ -27,10 +27,10 @@ func TestRecoveryMiddleware(t *testing.T) {
}) })
Convey("Given a non-api route that panics", t, func() { Convey("Given a non-api route that panics", t, func() {
apiUrl := "/whatever" apiURL := "/whatever"
recoveryScenario("recovery middleware should return html", apiUrl, func(sc *scenarioContext) { recoveryScenario("recovery middleware should return html", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler sc.handlerFunc = PanicHandler
sc.fakeReq("GET", apiUrl).exec() sc.fakeReq("GET", apiURL).exec()
So(sc.resp.Code, ShouldEqual, 500) So(sc.resp.Code, ShouldEqual, 500)
So(sc.resp.Header().Get("content-type"), ShouldEqual, "text/html; charset=UTF-8") So(sc.resp.Header().Get("content-type"), ShouldEqual, "text/html; charset=UTF-8")
@@ -63,7 +63,7 @@ func recoveryScenario(desc string, url string, fn scenarioFunc) {
sc.m.Use(GetContextHandler()) sc.m.Use(GetContextHandler())
// mock out gc goroutine // mock out gc goroutine
session.StartSessionGC = func() {} session.StartSessionGC = func() {}
sc.m.Use(Sessioner(&ms.Options{})) sc.m.Use(Sessioner(&ms.Options{}, 0))
sc.m.Use(OrgRedirect()) sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders()) sc.m.Use(AddDefaultResponseHeaders())
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"github.com/grafana/grafana/pkg/services/session" "github.com/grafana/grafana/pkg/services/session"
) )
func Sessioner(options *ms.Options) macaron.Handler { func Sessioner(options *ms.Options, sessionConnMaxLifetime int64) macaron.Handler {
session.Init(options) session.Init(options, sessionConnMaxLifetime)
return func(ctx *m.ReqContext) { return func(ctx *m.ReqContext) {
ctx.Next() ctx.Next()
@@ -108,6 +108,7 @@ func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
p.expire = expire p.expire = expire
p.c, err = sql.Open("mysql", connStr) p.c, err = sql.Open("mysql", connStr)
p.c.SetConnMaxLifetime(time.Second * time.Duration(sessionConnMaxLifetime))
if err != nil { if err != nil {
return err return err
} }
@@ -141,12 +142,29 @@ func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
// Exist returns true if session with given ID exists. // Exist returns true if session with given ID exists.
func (p *MysqlProvider) Exist(sid string) bool { func (p *MysqlProvider) Exist(sid string) bool {
exists, err := p.queryExists(sid)
if err != nil {
exists, err = p.queryExists(sid)
}
if err != nil {
log.Printf("session/mysql: error checking if session exists: %v", err)
return false
}
return exists
}
func (p *MysqlProvider) queryExists(sid string) (bool, error) {
var data []byte var data []byte
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data) err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
panic("session/mysql: error checking existence: " + err.Error()) return false, err
} }
return err != sql.ErrNoRows
return err != sql.ErrNoRows, nil
} }
// Destory deletes a session by session ID. // Destory deletes a session by session ID.
@@ -185,7 +203,12 @@ func (p *MysqlProvider) Count() (total int) {
// GC calls GC to clean expired sessions. // GC calls GC to clean expired sessions.
func (p *MysqlProvider) GC() { func (p *MysqlProvider) GC() {
if _, err := p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil { var err error
if _, err = p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil {
_, err = p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire)
}
if err != nil {
log.Printf("session/mysql: error garbage collecting: %v", err) log.Printf("session/mysql: error garbage collecting: %v", err)
} }
} }
+3 -2
View File
@@ -6,7 +6,6 @@ import (
ms "github.com/go-macaron/session" ms "github.com/go-macaron/session"
_ "github.com/go-macaron/session/memcache" _ "github.com/go-macaron/session/memcache"
_ "github.com/go-macaron/session/mysql"
_ "github.com/go-macaron/session/postgres" _ "github.com/go-macaron/session/postgres"
_ "github.com/go-macaron/session/redis" _ "github.com/go-macaron/session/redis"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
@@ -25,6 +24,7 @@ var sessionOptions *ms.Options
var StartSessionGC func() var StartSessionGC func()
var GetSessionCount func() int var GetSessionCount func() int
var sessionLogger = log.New("session") var sessionLogger = log.New("session")
var sessionConnMaxLifetime int64
func init() { func init() {
StartSessionGC = func() { StartSessionGC = func() {
@@ -37,9 +37,10 @@ func init() {
} }
} }
func Init(options *ms.Options) { func Init(options *ms.Options, connMaxLifetime int64) {
var err error var err error
sessionOptions = prepareOptions(options) sessionOptions = prepareOptions(options)
sessionConnMaxLifetime = connMaxLifetime
sessionManager, err = ms.NewManager(options.Provider, *options) sessionManager, err = ms.NewManager(options.Provider, *options)
if err != nil { if err != nil {
panic(err) panic(err)
+1 -1
View File
@@ -255,7 +255,7 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
} }
alert.State = cmd.State alert.State = cmd.State
alert.StateChanges += 1 alert.StateChanges++
alert.NewStateDate = timeNow() alert.NewStateDate = timeNow()
alert.EvalData = cmd.EvalData alert.EvalData = cmd.EvalData
@@ -46,6 +46,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id},
} }
err := SearchDashboards(query) err := SearchDashboards(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1) So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id) So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
+1 -1
View File
@@ -125,7 +125,7 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error {
condition := m.GetCondition() condition := m.GetCondition()
if condition != nil { if condition != nil {
sql, args := condition.Sql(mg.dialect) sql, args := condition.Sql(mg.dialect)
results, err := sess.Query(sql, args...) results, err := sess.SQL(sql).Query(args...)
if err != nil || len(results) == 0 { if err != nil || len(results) == 0 {
mg.Logger.Info("Skipping migration condition not fulfilled", "id", m.Id()) mg.Logger.Info("Skipping migration condition not fulfilled", "id", m.Id())
return sess.Rollback() return sess.Rollback()
+3
View File
@@ -2,6 +2,7 @@ package sqlstore
import ( import (
"testing" "testing"
"time"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -241,6 +242,8 @@ func TestAccountDataAccess(t *testing.T) {
func testHelperUpdateDashboardAcl(dashboardId int64, items ...m.DashboardAcl) error { func testHelperUpdateDashboardAcl(dashboardId int64, items ...m.DashboardAcl) error {
cmd := m.UpdateDashboardAclCommand{DashboardId: dashboardId} cmd := m.UpdateDashboardAclCommand{DashboardId: dashboardId}
for _, item := range items { for _, item := range items {
item.Created = time.Now()
item.Updated = time.Now()
cmd.Items = append(cmd.Items, &item) cmd.Items = append(cmd.Items, &item)
} }
return UpdateDashboardAcl(&cmd) return UpdateDashboardAcl(&cmd)
+9 -4
View File
@@ -2,6 +2,7 @@ package sqlstore
import ( import (
"fmt" "fmt"
"time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
@@ -98,8 +99,9 @@ func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error {
return inTransaction(func(sess *DBSession) error { return inTransaction(func(sess *DBSession) error {
//Check if quota is already defined in the DB //Check if quota is already defined in the DB
quota := m.Quota{ quota := m.Quota{
Target: cmd.Target, Target: cmd.Target,
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
Updated: time.Now(),
} }
has, err := sess.Get(&quota) has, err := sess.Get(&quota)
if err != nil { if err != nil {
@@ -107,6 +109,7 @@ func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error {
} }
quota.Limit = cmd.Limit quota.Limit = cmd.Limit
if has == false { if has == false {
quota.Created = time.Now()
//No quota in the DB for this target, so create a new one. //No quota in the DB for this target, so create a new one.
if _, err := sess.Insert(&quota); err != nil { if _, err := sess.Insert(&quota); err != nil {
return err return err
@@ -198,8 +201,9 @@ func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error {
return inTransaction(func(sess *DBSession) error { return inTransaction(func(sess *DBSession) error {
//Check if quota is already defined in the DB //Check if quota is already defined in the DB
quota := m.Quota{ quota := m.Quota{
Target: cmd.Target, Target: cmd.Target,
UserId: cmd.UserId, UserId: cmd.UserId,
Updated: time.Now(),
} }
has, err := sess.Get(&quota) has, err := sess.Get(&quota)
if err != nil { if err != nil {
@@ -207,6 +211,7 @@ func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error {
} }
quota.Limit = cmd.Limit quota.Limit = cmd.Limit
if has == false { if has == false {
quota.Created = time.Now()
//No quota in the DB for this target, so create a new one. //No quota in the DB for this target, so create a new one.
if _, err := sess.Insert(&quota); err != nil { if _, err := sess.Insert(&quota); err != nil {
return err return err
+2 -2
View File
@@ -104,12 +104,12 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
}) })
}) })
Convey("Given saved user quota for org", func() { Convey("Given saved user quota for org", func() {
userQoutaCmd := m.UpdateUserQuotaCmd{ userQuotaCmd := m.UpdateUserQuotaCmd{
UserId: userId, UserId: userId,
Target: "org_user", Target: "org_user",
Limit: 10, Limit: 10,
} }
err := UpdateUserQuota(&userQoutaCmd) err := UpdateUserQuota(&userQuotaCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Should be able to get saved quota by user id and target", func() { Convey("Should be able to get saved quota by user id and target", func() {
+21 -13
View File
@@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
@@ -35,6 +36,7 @@ type DatabaseConfig struct {
ServerCertName string ServerCertName string
MaxOpenConn int MaxOpenConn int
MaxIdleConn int MaxIdleConn int
ConnMaxLifetime int
} }
var ( var (
@@ -159,18 +161,20 @@ func getEngine() (*xorm.Engine, error) {
engine, err := xorm.NewEngine(DbCfg.Type, cnnstr) engine, err := xorm.NewEngine(DbCfg.Type, cnnstr)
if err != nil { if err != nil {
return nil, err return nil, err
} else {
engine.SetMaxOpenConns(DbCfg.MaxOpenConn)
engine.SetMaxIdleConns(DbCfg.MaxIdleConn)
debugSql := setting.Cfg.Section("database").Key("log_queries").MustBool(false)
if !debugSql {
engine.SetLogger(&xorm.DiscardLogger{})
} else {
engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm")))
engine.ShowSQL(true)
engine.ShowExecTime(true)
}
} }
engine.SetMaxOpenConns(DbCfg.MaxOpenConn)
engine.SetMaxIdleConns(DbCfg.MaxIdleConn)
engine.SetConnMaxLifetime(time.Second * time.Duration(DbCfg.ConnMaxLifetime))
debugSql := setting.Cfg.Section("database").Key("log_queries").MustBool(false)
if !debugSql {
engine.SetLogger(&xorm.DiscardLogger{})
} else {
engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm")))
engine.ShowSQL(true)
engine.ShowExecTime(true)
}
return engine, nil return engine, nil
} }
@@ -204,6 +208,7 @@ func LoadConfig() {
} }
DbCfg.MaxOpenConn = sec.Key("max_open_conn").MustInt(0) DbCfg.MaxOpenConn = sec.Key("max_open_conn").MustInt(0)
DbCfg.MaxIdleConn = sec.Key("max_idle_conn").MustInt(0) DbCfg.MaxIdleConn = sec.Key("max_idle_conn").MustInt(0)
DbCfg.ConnMaxLifetime = sec.Key("conn_max_lifetime").MustInt(14400)
if DbCfg.Type == "sqlite3" { if DbCfg.Type == "sqlite3" {
UseSQLite3 = true UseSQLite3 = true
@@ -227,8 +232,8 @@ var (
func InitTestDB(t *testing.T) *xorm.Engine { func InitTestDB(t *testing.T) *xorm.Engine {
selectedDb := dbSqlite selectedDb := dbSqlite
//selectedDb := dbMySql // selectedDb := dbMySql
//selectedDb := dbPostgres // selectedDb := dbPostgres
var x *xorm.Engine var x *xorm.Engine
var err error var err error
@@ -247,6 +252,9 @@ func InitTestDB(t *testing.T) *xorm.Engine {
x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr) x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
} }
x.DatabaseTZ = time.UTC
x.TZLocation = time.UTC
// x.ShowSQL() // x.ShowSQL()
if err != nil { if err != nil {
+4 -1
View File
@@ -131,7 +131,8 @@ var (
PluginAppsSkipVerifyTLS bool PluginAppsSkipVerifyTLS bool
// Session settings. // Session settings.
SessionOptions session.Options SessionOptions session.Options
SessionConnMaxLifetime int64
// Global setting objects. // Global setting objects.
Cfg *ini.File Cfg *ini.File
@@ -634,6 +635,8 @@ func readSessionConfig() {
if SessionOptions.CookiePath == "" { if SessionOptions.CookiePath == "" {
SessionOptions.CookiePath = "/" SessionOptions.CookiePath = "/"
} }
SessionConnMaxLifetime = Cfg.Section("session").Key("conn_max_lifetime").MustInt64(14400)
} }
func initLogging() { func initLogging() {
@@ -21,6 +21,8 @@
* data-tab-size - Tab size, default is 2. * data-tab-size - Tab size, default is 2.
* data-behaviours-enabled - Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of * data-behaviours-enabled - Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of
* special characters, like quotation marks, parenthesis, or brackets. * special characters, like quotation marks, parenthesis, or brackets.
* data-snippets-enabled - Specifies whether to use snippets or not. "Snippets" are small pieces of code that can be
* inserted via the completion box.
* *
* Keybindings: * Keybindings:
* Ctrl-Enter (Command-Enter): run onChange() function * Ctrl-Enter (Command-Enter): run onChange() function
@@ -49,6 +51,7 @@ const DEFAULT_MODE = 'text';
const DEFAULT_MAX_LINES = 10; const DEFAULT_MAX_LINES = 10;
const DEFAULT_TAB_SIZE = 2; const DEFAULT_TAB_SIZE = 2;
const DEFAULT_BEHAVIOURS = true; const DEFAULT_BEHAVIOURS = true;
const DEFAULT_SNIPPETS = true;
let editorTemplate = `<div></div>`; let editorTemplate = `<div></div>`;
@@ -59,6 +62,7 @@ function link(scope, elem, attrs) {
let showGutter = attrs.showGutter !== undefined; let showGutter = attrs.showGutter !== undefined;
let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE; let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
let behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS; let behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS;
let snippetsEnabled = attrs.snippetsEnabled ? attrs.snippetsEnabled === 'true' : DEFAULT_SNIPPETS;
// Initialize editor // Initialize editor
let aceElem = elem.get(0); let aceElem = elem.get(0);
@@ -143,7 +147,7 @@ function link(scope, elem, attrs) {
codeEditor.setOptions({ codeEditor.setOptions({
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
enableLiveAutocompletion: true, enableLiveAutocompletion: true,
enableSnippets: true, enableSnippets: snippetsEnabled,
}); });
if (scope.getCompleter()) { if (scope.getCompleter()) {
@@ -178,6 +178,14 @@ export class KeybindingSrv {
} }
}); });
// duplicate panel
this.bind('p d', () => {
if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
let panelIndex = dashboard.getPanelInfoById(dashboard.meta.focusPanelId).index;
dashboard.duplicatePanel(dashboard.panels[panelIndex]);
}
});
// share panel // share panel
this.bind('p s', () => { this.bind('p s', () => {
if (dashboard.meta.focusPanelId) { if (dashboard.meta.focusPanelId) {
@@ -30,7 +30,10 @@ describe('when updating view state', function() {
beforeEach( beforeEach(
angularMocks.inject(function(dashboardViewStateSrv, $location, $rootScope) { angularMocks.inject(function(dashboardViewStateSrv, $location, $rootScope) {
$rootScope.onAppEvent = function() {}; $rootScope.onAppEvent = function() {};
$rootScope.dashboard = { meta: {} }; $rootScope.dashboard = {
meta: {},
panels: [],
};
viewState = dashboardViewStateSrv.create($rootScope); viewState = dashboardViewStateSrv.create($rootScope);
location = $location; location = $location;
}) })
@@ -1,6 +1,7 @@
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import config from 'app/core/config'; import config from 'app/core/config';
import { DashboardModel } from './dashboard_model';
// represents the transient view state // represents the transient view state
// like fullscreen panel & edit // like fullscreen panel & edit
@@ -8,7 +9,7 @@ export class DashboardViewState {
state: any; state: any;
panelScopes: any; panelScopes: any;
$scope: any; $scope: any;
dashboard: any; dashboard: DashboardModel;
editStateChanged: any; editStateChanged: any;
fullscreenPanel: any; fullscreenPanel: any;
oldTimeRange: any; oldTimeRange: any;
@@ -89,6 +90,12 @@ export class DashboardViewState {
} }
} }
if ((this.state.fullscreen || this.dashboard.meta.soloMode) && this.state.panelId) {
// Trying to render panel in fullscreen when it's in the collapsed row causes an issue.
// So in this case expand collapsed row first.
this.toggleCollapsedPanelRow(this.state.panelId);
}
// if no edit state cleanup tab parm // if no edit state cleanup tab parm
if (!this.state.edit) { if (!this.state.edit) {
delete this.state.tab; delete this.state.tab;
@@ -103,6 +110,19 @@ export class DashboardViewState {
this.syncState(); this.syncState();
} }
toggleCollapsedPanelRow(panelId) {
for (let panel of this.dashboard.panels) {
if (panel.collapsed) {
for (let rowPanel of panel.panels) {
if (rowPanel.id === panelId) {
this.dashboard.toggleRow(panel);
return;
}
}
}
}
}
syncState() { syncState() {
if (this.panelScopes.length === 0) { if (this.panelScopes.length === 0) {
return; return;
@@ -78,8 +78,11 @@ class MetricsPanelCtrl extends PanelCtrl {
data = data.data; data = data.data;
} }
this.events.emit('data-snapshot-load', data); // Defer panel rendering till the next digest cycle.
return; // For some reason snapshot panels don't init at this time, so this helps to avoid rendering issues.
return this.$timeout(() => {
this.events.emit('data-snapshot-load', data);
});
} }
// // ignore if we have data stream // // ignore if we have data stream
@@ -1,155 +0,0 @@
define(['angular', 'lodash', 'jquery', 'rst2html', 'tether-drop'], function(angular, _, $, rst2html, Drop) {
'use strict';
angular.module('grafana.directives').directive('graphiteAddFunc', function($compile) {
var inputTemplate =
'<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
var buttonTemplate =
'<a class="gf-form-label query-part dropdown-toggle"' +
' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown">' +
'<i class="fa fa-plus"></i></a>';
return {
link: function($scope, elem) {
var ctrl = $scope.ctrl;
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
$input.appendTo(elem);
$button.appendTo(elem);
ctrl.datasource.getFuncDefs().then(function(funcDefs) {
var allFunctions = _.map(funcDefs, 'name').sort();
$scope.functionMenu = createFunctionDropDownMenu(funcDefs);
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: allFunctions,
minLength: 1,
items: 10,
updater: function(value) {
var funcDef = ctrl.datasource.getFuncDef(value);
if (!funcDef) {
// try find close match
value = value.toLowerCase();
funcDef = _.find(allFunctions, function(funcName) {
return funcName.toLowerCase().indexOf(value) === 0;
});
if (!funcDef) {
return;
}
}
$scope.$apply(function() {
ctrl.addFunction(funcDef);
});
$input.trigger('blur');
return '';
},
});
$button.click(function() {
$button.hide();
$input.show();
$input.focus();
});
$input.keyup(function() {
elem.toggleClass('open', $input.val() === '');
});
$input.blur(function() {
// clicking the function dropdown menu wont
// work if you remove class at once
setTimeout(function() {
$input.val('');
$input.hide();
$button.show();
elem.removeClass('open');
}, 200);
});
$compile(elem.contents())($scope);
});
var drop;
var cleanUpDrop = function() {
if (drop) {
drop.destroy();
drop = null;
}
};
$(elem)
.on('mouseenter', 'ul.dropdown-menu li', function() {
cleanUpDrop();
var funcDef;
try {
funcDef = ctrl.datasource.getFuncDef($('a', this).text());
} catch (e) {
// ignore
}
if (funcDef && funcDef.description) {
var shortDesc = funcDef.description;
if (shortDesc.length > 500) {
shortDesc = shortDesc.substring(0, 497) + '...';
}
var contentElement = document.createElement('div');
contentElement.innerHTML = '<h4>' + funcDef.name + '</h4>' + rst2html(shortDesc);
drop = new Drop({
target: this,
content: contentElement,
classes: 'drop-popover',
openOn: 'always',
tetherOptions: {
attachment: 'bottom left',
targetAttachment: 'bottom right',
},
});
}
})
.on('mouseout', 'ul.dropdown-menu li', function() {
cleanUpDrop();
});
$scope.$on('$destroy', cleanUpDrop);
},
};
});
function createFunctionDropDownMenu(funcDefs) {
var categories = {};
_.forEach(funcDefs, function(funcDef) {
if (!funcDef.category) {
return;
}
if (!categories[funcDef.category]) {
categories[funcDef.category] = [];
}
categories[funcDef.category].push({
text: funcDef.name,
click: "ctrl.addFunction('" + funcDef.name + "')",
});
});
return _.sortBy(
_.map(categories, function(submenu, category) {
return {
text: category,
submenu: _.sortBy(submenu, 'text'),
};
}),
'text'
);
}
});
@@ -0,0 +1,159 @@
import angular from 'angular';
import _ from 'lodash';
import $ from 'jquery';
import rst2html from 'rst2html';
import Drop from 'tether-drop';
export function graphiteAddFunc($compile) {
const inputTemplate =
'<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
const buttonTemplate =
'<a class="gf-form-label query-part dropdown-toggle"' +
' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown">' +
'<i class="fa fa-plus"></i></a>';
return {
link: function($scope, elem) {
var ctrl = $scope.ctrl;
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
$input.appendTo(elem);
$button.appendTo(elem);
ctrl.datasource.getFuncDefs().then(function(funcDefs) {
var allFunctions = _.map(funcDefs, 'name').sort();
$scope.functionMenu = createFunctionDropDownMenu(funcDefs);
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: allFunctions,
minLength: 1,
items: 10,
updater: function(value) {
var funcDef = ctrl.datasource.getFuncDef(value);
if (!funcDef) {
// try find close match
value = value.toLowerCase();
funcDef = _.find(allFunctions, function(funcName) {
return funcName.toLowerCase().indexOf(value) === 0;
});
if (!funcDef) {
return '';
}
}
$scope.$apply(function() {
ctrl.addFunction(funcDef);
});
$input.trigger('blur');
return '';
},
});
$button.click(function() {
$button.hide();
$input.show();
$input.focus();
});
$input.keyup(function() {
elem.toggleClass('open', $input.val() === '');
});
$input.blur(function() {
// clicking the function dropdown menu wont
// work if you remove class at once
setTimeout(function() {
$input.val('');
$input.hide();
$button.show();
elem.removeClass('open');
}, 200);
});
$compile(elem.contents())($scope);
});
var drop;
var cleanUpDrop = function() {
if (drop) {
drop.destroy();
drop = null;
}
};
$(elem)
.on('mouseenter', 'ul.dropdown-menu li', function() {
cleanUpDrop();
var funcDef;
try {
funcDef = ctrl.datasource.getFuncDef($('a', this).text());
} catch (e) {
// ignore
}
if (funcDef && funcDef.description) {
var shortDesc = funcDef.description;
if (shortDesc.length > 500) {
shortDesc = shortDesc.substring(0, 497) + '...';
}
var contentElement = document.createElement('div');
contentElement.innerHTML = '<h4>' + funcDef.name + '</h4>' + rst2html(shortDesc);
drop = new Drop({
target: this,
content: contentElement,
classes: 'drop-popover',
openOn: 'always',
tetherOptions: {
attachment: 'bottom left',
targetAttachment: 'bottom right',
},
});
}
})
.on('mouseout', 'ul.dropdown-menu li', function() {
cleanUpDrop();
});
$scope.$on('$destroy', cleanUpDrop);
},
};
}
angular.module('grafana.directives').directive('graphiteAddFunc', graphiteAddFunc);
function createFunctionDropDownMenu(funcDefs) {
var categories = {};
_.forEach(funcDefs, function(funcDef) {
if (!funcDef.category) {
return;
}
if (!categories[funcDef.category]) {
categories[funcDef.category] = [];
}
categories[funcDef.category].push({
text: funcDef.name,
click: "ctrl.addFunction('" + funcDef.name + "')",
});
});
return _.sortBy(
_.map(categories, function(submenu, category) {
return {
text: category,
submenu: _.sortBy(submenu, 'text'),
};
}),
'text'
);
}
@@ -1,309 +0,0 @@
define([
'angular',
'lodash',
'jquery',
'rst2html',
],
function (angular, _, $, rst2html) {
'use strict';
angular
.module('grafana.directives')
.directive('graphiteFuncEditor', function($compile, templateSrv, popoverSrv) {
var funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
var paramTemplate = '<input type="text" style="display:none"' +
' class="input-small tight-form-func-param"></input>';
var funcControlsTemplate =
'<div class="tight-form-func-controls">' +
'<span class="pointer fa fa-arrow-left"></span>' +
'<span class="pointer fa fa-question-circle"></span>' +
'<span class="pointer fa fa-remove" ></span>' +
'<span class="pointer fa fa-arrow-right"></span>' +
'</div>';
return {
restrict: 'A',
link: function postLink($scope, elem) {
var $funcLink = $(funcSpanTemplate);
var $funcControls = $(funcControlsTemplate);
var ctrl = $scope.ctrl;
var func = $scope.func;
var scheduledRelink = false;
var paramCountAtLink = 0;
var cancelBlur = null;
function clickFuncParam(paramIndex) {
/*jshint validthis:true */
var $link = $(this);
var $comma = $link.prev('.comma');
var $input = $link.next();
$input.val(func.params[paramIndex]);
$comma.removeClass('query-part__last');
$link.hide();
$input.show();
$input.focus();
$input.select();
var typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
}
function scheduledRelinkIfNeeded() {
if (paramCountAtLink === func.params.length) {
return;
}
if (!scheduledRelink) {
scheduledRelink = true;
setTimeout(function() {
relink();
scheduledRelink = false;
}, 200);
}
}
function paramDef(index) {
if (index < func.def.params.length) {
return func.def.params[index];
}
if (_.last(func.def.params).multiple) {
return _.assign({}, _.last(func.def.params), {optional: true});
}
return {};
}
function switchToLink(inputElem, paramIndex) {
/*jshint validthis:true */
var $input = $(inputElem);
clearTimeout(cancelBlur);
cancelBlur = null;
var $link = $input.prev();
var $comma = $link.prev('.comma');
var newValue = $input.val();
// remove optional empty params
if (newValue !== '' || paramDef(paramIndex).optional) {
func.updateParam(newValue, paramIndex);
$link.html(newValue ? templateSrv.highlightVariablesAsHtml(newValue) : '&nbsp;');
}
scheduledRelinkIfNeeded();
$scope.$apply(function() {
ctrl.targetChanged();
});
if ($link.hasClass('query-part__last') && newValue === '') {
$comma.addClass('query-part__last');
} else {
$link.removeClass('query-part__last');
}
$input.hide();
$link.show();
}
// this = input element
function inputBlur(paramIndex) {
/*jshint validthis:true */
var inputElem = this;
// happens long before the click event on the typeahead options
// need to have long delay because the blur
cancelBlur = setTimeout(function() {
switchToLink(inputElem, paramIndex);
}, 200);
}
function inputKeyPress(paramIndex, e) {
/*jshint validthis:true */
if(e.which === 13) {
$(this).blur();
}
}
function inputKeyDown() {
/*jshint validthis:true */
this.style.width = (3 + this.value.length) * 8 + 'px';
}
function addTypeahead($input, paramIndex) {
$input.attr('data-provide', 'typeahead');
var options = paramDef(paramIndex).options;
if (paramDef(paramIndex).type === 'int') {
options = _.map(options, function(val) { return val.toString(); });
}
$input.typeahead({
source: options,
minLength: 0,
items: 20,
updater: function (value) {
$input.val(value);
switchToLink($input[0], paramIndex);
return value;
}
});
var typeahead = $input.data('typeahead');
typeahead.lookup = function () {
this.query = this.$element.val() || '';
return this.process(this.source);
};
}
function toggleFuncControls() {
var targetDiv = elem.closest('.tight-form');
if (elem.hasClass('show-function-controls')) {
elem.removeClass('show-function-controls');
targetDiv.removeClass('has-open-function');
$funcControls.hide();
return;
}
elem.addClass('show-function-controls');
targetDiv.addClass('has-open-function');
$funcControls.show();
}
function addElementsAndCompile() {
$funcControls.appendTo(elem);
$funcLink.appendTo(elem);
var defParams = _.clone(func.def.params);
var lastParam = _.last(func.def.params);
while (func.params.length >= defParams.length && lastParam && lastParam.multiple) {
defParams.push(_.assign({}, lastParam, {optional: true}));
}
_.each(defParams, function(param, index) {
if (param.optional && func.params.length < index) {
return false;
}
var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
var last = (index >= func.params.length - 1) && param.optional && !paramValue;
if (last && param.multiple) {
paramValue = '+';
}
if (index > 0) {
$('<span class="comma' + (last ? ' query-part__last' : '') + '">, </span>').appendTo(elem);
}
var $paramLink = $(
'<a ng-click="" class="graphite-func-param-link' + (last ? ' query-part__last' : '') + '">'
+ (paramValue || '&nbsp;') + '</a>');
var $input = $(paramTemplate);
$input.attr('placeholder', param.name);
paramCountAtLink++;
$paramLink.appendTo(elem);
$input.appendTo(elem);
$input.blur(_.partial(inputBlur, index));
$input.keyup(inputKeyDown);
$input.keypress(_.partial(inputKeyPress, index));
$paramLink.click(_.partial(clickFuncParam, index));
if (param.options) {
addTypeahead($input, index);
}
});
$('<span>)</span>').appendTo(elem);
$compile(elem.contents())($scope);
}
function ifJustAddedFocusFirstParam() {
if ($scope.func.added) {
$scope.func.added = false;
setTimeout(function() {
elem.find('.graphite-func-param-link').first().click();
}, 10);
}
}
function registerFuncControlsToggle() {
$funcLink.click(toggleFuncControls);
}
function registerFuncControlsActions() {
$funcControls.click(function(e) {
var $target = $(e.target);
if ($target.hasClass('fa-remove')) {
toggleFuncControls();
$scope.$apply(function() {
ctrl.removeFunction($scope.func);
});
return;
}
if ($target.hasClass('fa-arrow-left')) {
$scope.$apply(function() {
_.move(ctrl.queryModel.functions, $scope.$index, $scope.$index - 1);
ctrl.targetChanged();
});
return;
}
if ($target.hasClass('fa-arrow-right')) {
$scope.$apply(function() {
_.move(ctrl.queryModel.functions, $scope.$index, $scope.$index + 1);
ctrl.targetChanged();
});
return;
}
if ($target.hasClass('fa-question-circle')) {
var funcDef = ctrl.datasource.getFuncDef(func.def.name);
if (funcDef && funcDef.description) {
popoverSrv.show({
element: e.target,
position: 'bottom left',
classNames: 'drop-popover drop-function-def',
template: '<div style="overflow:auto;max-height:30rem;">'
+ '<h4>' + funcDef.name + '</h4>' + rst2html(funcDef.description) + '</div>',
openOn: 'click',
});
} else {
window.open(
"http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + func.def.name,'_blank');
}
return;
}
});
}
function relink() {
elem.children().remove();
addElementsAndCompile();
ifJustAddedFocusFirstParam();
registerFuncControlsToggle();
registerFuncControlsActions();
}
relink();
}
};
});
});
@@ -0,0 +1,317 @@
import angular from 'angular';
import _ from 'lodash';
import $ from 'jquery';
import rst2html from 'rst2html';
export function graphiteFuncEditor($compile, templateSrv, popoverSrv) {
const funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
const paramTemplate =
'<input type="text" style="display:none"' + ' class="input-small tight-form-func-param"></input>';
const funcControlsTemplate = `
<div class="tight-form-func-controls">
<span class="pointer fa fa-arrow-left"></span>
<span class="pointer fa fa-question-circle"></span>
<span class="pointer fa fa-remove" ></span>
<span class="pointer fa fa-arrow-right"></span>
</div>`;
return {
restrict: 'A',
link: function postLink($scope, elem) {
var $funcLink = $(funcSpanTemplate);
var $funcControls = $(funcControlsTemplate);
var ctrl = $scope.ctrl;
var func = $scope.func;
var scheduledRelink = false;
var paramCountAtLink = 0;
var cancelBlur = null;
function clickFuncParam(paramIndex) {
/*jshint validthis:true */
var $link = $(this);
var $comma = $link.prev('.comma');
var $input = $link.next();
$input.val(func.params[paramIndex]);
$comma.removeClass('query-part__last');
$link.hide();
$input.show();
$input.focus();
$input.select();
var typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
}
function scheduledRelinkIfNeeded() {
if (paramCountAtLink === func.params.length) {
return;
}
if (!scheduledRelink) {
scheduledRelink = true;
setTimeout(function() {
relink();
scheduledRelink = false;
}, 200);
}
}
function paramDef(index) {
if (index < func.def.params.length) {
return func.def.params[index];
}
if (_.last(func.def.params).multiple) {
return _.assign({}, _.last(func.def.params), { optional: true });
}
return {};
}
function switchToLink(inputElem, paramIndex) {
/*jshint validthis:true */
var $input = $(inputElem);
clearTimeout(cancelBlur);
cancelBlur = null;
var $link = $input.prev();
var $comma = $link.prev('.comma');
var newValue = $input.val();
// remove optional empty params
if (newValue !== '' || paramDef(paramIndex).optional) {
func.updateParam(newValue, paramIndex);
$link.html(newValue ? templateSrv.highlightVariablesAsHtml(newValue) : '&nbsp;');
}
scheduledRelinkIfNeeded();
$scope.$apply(function() {
ctrl.targetChanged();
});
if ($link.hasClass('query-part__last') && newValue === '') {
$comma.addClass('query-part__last');
} else {
$link.removeClass('query-part__last');
}
$input.hide();
$link.show();
}
// this = input element
function inputBlur(paramIndex) {
/*jshint validthis:true */
var inputElem = this;
// happens long before the click event on the typeahead options
// need to have long delay because the blur
cancelBlur = setTimeout(function() {
switchToLink(inputElem, paramIndex);
}, 200);
}
function inputKeyPress(paramIndex, e) {
/*jshint validthis:true */
if (e.which === 13) {
$(this).blur();
}
}
function inputKeyDown() {
/*jshint validthis:true */
this.style.width = (3 + this.value.length) * 8 + 'px';
}
function addTypeahead($input, paramIndex) {
$input.attr('data-provide', 'typeahead');
var options = paramDef(paramIndex).options;
if (paramDef(paramIndex).type === 'int') {
options = _.map(options, function(val) {
return val.toString();
});
}
$input.typeahead({
source: options,
minLength: 0,
items: 20,
updater: function(value) {
$input.val(value);
switchToLink($input[0], paramIndex);
return value;
},
});
var typeahead = $input.data('typeahead');
typeahead.lookup = function() {
this.query = this.$element.val() || '';
return this.process(this.source);
};
}
function toggleFuncControls() {
var targetDiv = elem.closest('.tight-form');
if (elem.hasClass('show-function-controls')) {
elem.removeClass('show-function-controls');
targetDiv.removeClass('has-open-function');
$funcControls.hide();
return;
}
elem.addClass('show-function-controls');
targetDiv.addClass('has-open-function');
$funcControls.show();
}
function addElementsAndCompile() {
$funcControls.appendTo(elem);
$funcLink.appendTo(elem);
var defParams = _.clone(func.def.params);
var lastParam = _.last(func.def.params);
while (func.params.length >= defParams.length && lastParam && lastParam.multiple) {
defParams.push(_.assign({}, lastParam, { optional: true }));
}
_.each(defParams, function(param, index) {
if (param.optional && func.params.length < index) {
return false;
}
var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
var last = index >= func.params.length - 1 && param.optional && !paramValue;
if (last && param.multiple) {
paramValue = '+';
}
if (index > 0) {
$('<span class="comma' + (last ? ' query-part__last' : '') + '">, </span>').appendTo(elem);
}
var $paramLink = $(
'<a ng-click="" class="graphite-func-param-link' +
(last ? ' query-part__last' : '') +
'">' +
(paramValue || '&nbsp;') +
'</a>'
);
var $input = $(paramTemplate);
$input.attr('placeholder', param.name);
paramCountAtLink++;
$paramLink.appendTo(elem);
$input.appendTo(elem);
$input.blur(_.partial(inputBlur, index));
$input.keyup(inputKeyDown);
$input.keypress(_.partial(inputKeyPress, index));
$paramLink.click(_.partial(clickFuncParam, index));
if (param.options) {
addTypeahead($input, index);
}
return true;
});
$('<span>)</span>').appendTo(elem);
$compile(elem.contents())($scope);
}
function ifJustAddedFocusFirstParam() {
if ($scope.func.added) {
$scope.func.added = false;
setTimeout(function() {
elem
.find('.graphite-func-param-link')
.first()
.click();
}, 10);
}
}
function registerFuncControlsToggle() {
$funcLink.click(toggleFuncControls);
}
function registerFuncControlsActions() {
$funcControls.click(function(e) {
var $target = $(e.target);
if ($target.hasClass('fa-remove')) {
toggleFuncControls();
$scope.$apply(function() {
ctrl.removeFunction($scope.func);
});
return;
}
if ($target.hasClass('fa-arrow-left')) {
$scope.$apply(function() {
_.move(ctrl.queryModel.functions, $scope.$index, $scope.$index - 1);
ctrl.targetChanged();
});
return;
}
if ($target.hasClass('fa-arrow-right')) {
$scope.$apply(function() {
_.move(ctrl.queryModel.functions, $scope.$index, $scope.$index + 1);
ctrl.targetChanged();
});
return;
}
if ($target.hasClass('fa-question-circle')) {
var funcDef = ctrl.datasource.getFuncDef(func.def.name);
if (funcDef && funcDef.description) {
popoverSrv.show({
element: e.target,
position: 'bottom left',
classNames: 'drop-popover drop-function-def',
template: `
<div style="overflow:auto;max-height:30rem;">
<h4> ${funcDef.name} </h4>
${rst2html(funcDef.description)}
</div>`,
openOn: 'click',
});
} else {
window.open(
'http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions.' + func.def.name,
'_blank'
);
}
return;
}
});
}
function relink() {
elem.children().remove();
addElementsAndCompile();
ifJustAddedFocusFirstParam();
registerFuncControlsToggle();
registerFuncControlsActions();
}
relink();
},
};
}
angular.module('grafana.directives').directive('graphiteFuncEditor', graphiteFuncEditor);
@@ -1,24 +1,26 @@
import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common'; import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common';
import moment from 'moment'; import moment from 'moment';
import helpers from 'test/specs/helpers'; import helpers from 'test/specs/helpers';
import {MssqlDatasource} from '../datasource'; import { MssqlDatasource } from '../datasource';
import {CustomVariable} from 'app/features/templating/custom_variable'; import { CustomVariable } from 'app/features/templating/custom_variable';
describe('MSSQLDatasource', function() { describe('MSSQLDatasource', function() {
var ctx = new helpers.ServiceTestContext(); var ctx = new helpers.ServiceTestContext();
var instanceSettings = {name: 'mssql'}; var instanceSettings = { name: 'mssql' };
beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.core'));
beforeEach(angularMocks.module('grafana.services')); beforeEach(angularMocks.module('grafana.services'));
beforeEach(ctx.providePhase(['backendSrv'])); beforeEach(ctx.providePhase(['backendSrv']));
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { beforeEach(
ctx.$q = $q; angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
ctx.$httpBackend = $httpBackend; ctx.$q = $q;
ctx.$rootScope = $rootScope; ctx.$httpBackend = $httpBackend;
ctx.ds = $injector.instantiate(MssqlDatasource, {instanceSettings: instanceSettings}); ctx.$rootScope = $rootScope;
$httpBackend.when('GET', /\.html$/).respond(''); ctx.ds = $injector.instantiate(MssqlDatasource, { instanceSettings: instanceSettings });
})); $httpBackend.when('GET', /\.html$/).respond('');
})
);
describe('When performing annotationQuery', function() { describe('When performing annotationQuery', function() {
let results; let results;
@@ -28,12 +30,12 @@ describe('MSSQLDatasource', function() {
const options = { const options = {
annotation: { annotation: {
name: annotationName, name: annotationName,
rawQuery: 'select time, text, tags from table;' rawQuery: 'select time, text, tags from table;',
}, },
range: { range: {
from: moment(1432288354), from: moment(1432288354),
to: moment(1432288401) to: moment(1432288401),
} },
}; };
const response = { const response = {
@@ -42,23 +44,25 @@ describe('MSSQLDatasource', function() {
refId: annotationName, refId: annotationName,
tables: [ tables: [
{ {
columns: [{text: 'time'}, {text: 'text'}, {text: 'tags'}], columns: [{ text: 'time' }, { text: 'text' }, { text: 'tags' }],
rows: [ rows: [
[1432288355, 'some text', 'TagA,TagB'], [1432288355, 'some text', 'TagA,TagB'],
[1432288390, 'some text2', ' TagB , TagC'], [1432288390, 'some text2', ' TagB , TagC'],
[1432288400, 'some text3'] [1432288400, 'some text3'],
] ],
} },
] ],
} },
} },
}; };
beforeEach(function() { beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(options) { ctx.backendSrv.datasourceRequest = function(options) {
return ctx.$q.when({data: response, status: 200}); return ctx.$q.when({ data: response, status: 200 });
}; };
ctx.ds.annotationQuery(options).then(function(data) { results = data; }); ctx.ds.annotationQuery(options).then(function(data) {
results = data;
});
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
}); });
@@ -83,28 +87,26 @@ describe('MSSQLDatasource', function() {
results: { results: {
tempvar: { tempvar: {
meta: { meta: {
rowCount: 3 rowCount: 3,
}, },
refId: 'tempvar', refId: 'tempvar',
tables: [ tables: [
{ {
columns: [{text: 'title'}, {text: 'text'}], columns: [{ text: 'title' }, { text: 'text' }],
rows: [ rows: [['aTitle', 'some text'], ['aTitle2', 'some text2'], ['aTitle3', 'some text3']],
['aTitle', 'some text'], },
['aTitle2', 'some text2'], ],
['aTitle3', 'some text3'] },
] },
}
]
}
}
}; };
beforeEach(function() { beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(options) { ctx.backendSrv.datasourceRequest = function(options) {
return ctx.$q.when({data: response, status: 200}); return ctx.$q.when({ data: response, status: 200 });
}; };
ctx.ds.metricFindQuery(query).then(function(data) { results = data; }); ctx.ds.metricFindQuery(query).then(function(data) {
results = data;
});
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
}); });
@@ -122,28 +124,26 @@ describe('MSSQLDatasource', function() {
results: { results: {
tempvar: { tempvar: {
meta: { meta: {
rowCount: 3 rowCount: 3,
}, },
refId: 'tempvar', refId: 'tempvar',
tables: [ tables: [
{ {
columns: [{text: '__value'}, {text: '__text'}], columns: [{ text: '__value' }, { text: '__text' }],
rows: [ rows: [['value1', 'aTitle'], ['value2', 'aTitle2'], ['value3', 'aTitle3']],
['value1', 'aTitle'], },
['value2', 'aTitle2'], ],
['value3', 'aTitle3'] },
] },
}
]
}
}
}; };
beforeEach(function() { beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(options) { ctx.backendSrv.datasourceRequest = function(options) {
return ctx.$q.when({data: response, status: 200}); return ctx.$q.when({ data: response, status: 200 });
}; };
ctx.ds.metricFindQuery(query).then(function(data) { results = data; }); ctx.ds.metricFindQuery(query).then(function(data) {
results = data;
});
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
}); });
@@ -163,28 +163,26 @@ describe('MSSQLDatasource', function() {
results: { results: {
tempvar: { tempvar: {
meta: { meta: {
rowCount: 3 rowCount: 3,
}, },
refId: 'tempvar', refId: 'tempvar',
tables: [ tables: [
{ {
columns: [{text: '__text'}, {text: '__value'}], columns: [{ text: '__text' }, { text: '__value' }],
rows: [ rows: [['aTitle', 'same'], ['aTitle', 'same'], ['aTitle', 'diff']],
['aTitle', 'same'], },
['aTitle', 'same'], ],
['aTitle', 'diff'] },
] },
}
]
}
}
}; };
beforeEach(function() { beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(options) { ctx.backendSrv.datasourceRequest = function(options) {
return ctx.$q.when({data: response, status: 200}); return ctx.$q.when({ data: response, status: 200 });
}; };
ctx.ds.metricFindQuery(query).then(function(data) { results = data; }); ctx.ds.metricFindQuery(query).then(function(data) {
results = data;
});
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();
}); });
@@ -197,7 +195,7 @@ describe('MSSQLDatasource', function() {
describe('When interpolating variables', () => { describe('When interpolating variables', () => {
beforeEach(function() { beforeEach(function() {
ctx.variable = new CustomVariable({},{}); ctx.variable = new CustomVariable({}, {});
}); });
describe('and value is a string', () => { describe('and value is a string', () => {
@@ -214,23 +212,22 @@ describe('MSSQLDatasource', function() {
describe('and value is an array of strings', () => { describe('and value is an array of strings', () => {
it('should return comma separated quoted values', () => { it('should return comma separated quoted values', () => {
expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).to.eql('\'a\',\'b\',\'c\''); expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).to.eql("'a','b','c'");
}); });
}); });
describe('and variable allows multi-value and value is a string', () => { describe('and variable allows multi-value and value is a string', () => {
it('should return a quoted value', () => { it('should return a quoted value', () => {
ctx.variable.multi = true; ctx.variable.multi = true;
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql('\'abc\''); expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql("'abc'");
}); });
}); });
describe('and variable allows all and value is a string', () => { describe('and variable allows all and value is a string', () => {
it('should return a quoted value', () => { it('should return a quoted value', () => {
ctx.variable.includeAll = true; ctx.variable.includeAll = true;
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql('\'abc\''); expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql("'abc'");
}); });
}); });
}); });
}); });
@@ -47,12 +47,9 @@ describe('Prometheus editor completer', function() {
variables: [ variables: [
{ {
name: 'var_name', name: 'var_name',
options: [ options: [{ text: 'foo', value: 'foo', selected: false }, { text: 'bar', value: 'bar', selected: true }],
{ text: 'foo', value: 'foo', selected: false }, },
{ text: 'bar', value: 'bar', selected: true } ],
]
}
]
}; };
let completer = new PromCompleter(datasourceStub, templateSrv); let completer = new PromCompleter(datasourceStub, templateSrv);
+6 -2
View File
@@ -26,7 +26,9 @@ export const ViewStore = types
function updateQuery(query: any) { function updateQuery(query: any) {
self.query.clear(); self.query.clear();
for (let key of Object.keys(query)) { for (let key of Object.keys(query)) {
self.query.set(key, query[key]); if (query[key]) {
self.query.set(key, query[key]);
}
} }
} }
@@ -34,7 +36,9 @@ export const ViewStore = types
function updateRouteParams(routeParams: any) { function updateRouteParams(routeParams: any) {
self.routeParams.clear(); self.routeParams.clear();
for (let key of Object.keys(routeParams)) { for (let key of Object.keys(routeParams)) {
self.routeParams.set(key, routeParams[key]); if (routeParams[key]) {
self.routeParams.set(key, routeParams[key]);
}
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

+4 -1
View File
@@ -13,12 +13,13 @@ const (
ONLYFROMDB ONLYFROMDB
) )
// database column // Column defines database column
type Column struct { type Column struct {
Name string Name string
TableName string TableName string
FieldName string FieldName string
SQLType SQLType SQLType SQLType
IsJSON bool
Length int Length int
Length2 int Length2 int
Nullable bool Nullable bool
@@ -37,6 +38,7 @@ type Column struct {
SetOptions map[string]int SetOptions map[string]int
DisableTimeZone bool DisableTimeZone bool
TimeZone *time.Location // column specified time zone TimeZone *time.Location // column specified time zone
Comment string
} }
func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column {
@@ -60,6 +62,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable
IsVersion: false, IsVersion: false,
DefaultIsEmpty: false, DefaultIsEmpty: false,
EnumOptions: make(map[string]int), EnumOptions: make(map[string]int),
Comment: "",
} }
} }
+3
View File
@@ -244,6 +244,9 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri
sql += col.StringNoPk(b.dialect) sql += col.StringNoPk(b.dialect)
} }
sql = strings.TrimSpace(sql) sql = strings.TrimSpace(sql)
if b.DriverName() == MYSQL && len(col.Comment) > 0 {
sql += " COMMENT '" + col.Comment + "'"
}
sql += ", " sql += ", "
} }
+12
View File
@@ -247,6 +247,18 @@ type Row struct {
err error // deferred error for easy chaining err error // deferred error for easy chaining
} }
// ErrorRow return an error row
func ErrorRow(err error) *Row {
return &Row{
err: err,
}
}
// NewRow from rows
func NewRow(rows *Rows, err error) *Row {
return &Row{rows, err}
}
func (row *Row) Columns() ([]string, error) { func (row *Row) Columns() ([]string, error) {
if row.err != nil { if row.err != nil {
return nil, row.err return nil, row.err
+1
View File
@@ -22,6 +22,7 @@ type Table struct {
Cacher Cacher Cacher Cacher
StoreEngine string StoreEngine string
Charset string Charset string
Comment string
} }
func (table *Table) Columns() []*Column { func (table *Table) Columns() []*Column {
+3 -2
View File
@@ -100,7 +100,8 @@ var (
LongBlob = "LONGBLOB" LongBlob = "LONGBLOB"
Bytea = "BYTEA" Bytea = "BYTEA"
Bool = "BOOL" Bool = "BOOL"
Boolean = "BOOLEAN"
Serial = "SERIAL" Serial = "SERIAL"
BigSerial = "BIGSERIAL" BigSerial = "BIGSERIAL"
@@ -163,7 +164,7 @@ var (
uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"} uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"}
) )
// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparision // !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison
var ( var (
c_EMPTY_STRING string c_EMPTY_STRING string
c_BOOL_DEFAULT bool c_BOOL_DEFAULT bool
@@ -15,13 +15,12 @@ import (
// LRUCacher implments cache object facilities // LRUCacher implments cache object facilities
type LRUCacher struct { type LRUCacher struct {
idList *list.List idList *list.List
sqlList *list.List sqlList *list.List
idIndex map[string]map[string]*list.Element idIndex map[string]map[string]*list.Element
sqlIndex map[string]map[string]*list.Element sqlIndex map[string]map[string]*list.Element
store core.CacheStore store core.CacheStore
mutex sync.Mutex mutex sync.Mutex
// maxSize int
MaxElementSize int MaxElementSize int
Expired time.Duration Expired time.Duration
GcInterval time.Duration GcInterval time.Duration
@@ -54,8 +53,6 @@ func (m *LRUCacher) RunGC() {
// GC check ids lit and sql list to remove all element expired // GC check ids lit and sql list to remove all element expired
func (m *LRUCacher) GC() { func (m *LRUCacher) GC() {
//fmt.Println("begin gc ...")
//defer fmt.Println("end gc ...")
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
var removedNum int var removedNum int
@@ -64,12 +61,10 @@ func (m *LRUCacher) GC() {
time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired { time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired {
removedNum++ removedNum++
next := e.Next() next := e.Next()
//fmt.Println("removing ...", e.Value)
node := e.Value.(*idNode) node := e.Value.(*idNode)
m.delBean(node.tbName, node.id) m.delBean(node.tbName, node.id)
e = next e = next
} else { } else {
//fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.idList.Len())
break break
} }
} }
@@ -80,12 +75,10 @@ func (m *LRUCacher) GC() {
time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired { time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired {
removedNum++ removedNum++
next := e.Next() next := e.Next()
//fmt.Println("removing ...", e.Value)
node := e.Value.(*sqlNode) node := e.Value.(*sqlNode)
m.delIds(node.tbName, node.sql) m.delIds(node.tbName, node.sql)
e = next e = next
} else { } else {
//fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.sqlList.Len())
break break
} }
} }
@@ -116,7 +109,6 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} {
} }
m.delIds(tableName, sql) m.delIds(tableName, sql)
return nil return nil
} }
@@ -134,7 +126,6 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} {
// if expired, remove the node and return nil // if expired, remove the node and return nil
if time.Now().Sub(lastTime) > m.Expired { if time.Now().Sub(lastTime) > m.Expired {
m.delBean(tableName, id) m.delBean(tableName, id)
//m.clearIds(tableName)
return nil return nil
} }
m.idList.MoveToBack(el) m.idList.MoveToBack(el)
@@ -148,7 +139,6 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} {
// store bean is not exist, then remove memory's index // store bean is not exist, then remove memory's index
m.delBean(tableName, id) m.delBean(tableName, id)
//m.clearIds(tableName)
return nil return nil
} }
@@ -166,8 +156,8 @@ func (m *LRUCacher) clearIds(tableName string) {
// ClearIds clears all sql-ids mapping on table tableName from cache // ClearIds clears all sql-ids mapping on table tableName from cache
func (m *LRUCacher) ClearIds(tableName string) { func (m *LRUCacher) ClearIds(tableName string) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock()
m.clearIds(tableName) m.clearIds(tableName)
m.mutex.Unlock()
} }
func (m *LRUCacher) clearBeans(tableName string) { func (m *LRUCacher) clearBeans(tableName string) {
@@ -184,14 +174,13 @@ func (m *LRUCacher) clearBeans(tableName string) {
// ClearBeans clears all beans in some table // ClearBeans clears all beans in some table
func (m *LRUCacher) ClearBeans(tableName string) { func (m *LRUCacher) ClearBeans(tableName string) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock()
m.clearBeans(tableName) m.clearBeans(tableName)
m.mutex.Unlock()
} }
// PutIds pus ids into table // PutIds pus ids into table
func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock()
if _, ok := m.sqlIndex[tableName]; !ok { if _, ok := m.sqlIndex[tableName]; !ok {
m.sqlIndex[tableName] = make(map[string]*list.Element) m.sqlIndex[tableName] = make(map[string]*list.Element)
} }
@@ -207,12 +196,12 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
node := e.Value.(*sqlNode) node := e.Value.(*sqlNode)
m.delIds(node.tbName, node.sql) m.delIds(node.tbName, node.sql)
} }
m.mutex.Unlock()
} }
// PutBean puts beans into table // PutBean puts beans into table
func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) { func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock()
var el *list.Element var el *list.Element
var ok bool var ok bool
@@ -229,6 +218,7 @@ func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) {
node := e.Value.(*idNode) node := e.Value.(*idNode)
m.delBean(node.tbName, node.id) m.delBean(node.tbName, node.id)
} }
m.mutex.Unlock()
} }
func (m *LRUCacher) delIds(tableName, sql string) { func (m *LRUCacher) delIds(tableName, sql string) {
@@ -244,8 +234,8 @@ func (m *LRUCacher) delIds(tableName, sql string) {
// DelIds deletes ids // DelIds deletes ids
func (m *LRUCacher) DelIds(tableName, sql string) { func (m *LRUCacher) DelIds(tableName, sql string) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock()
m.delIds(tableName, sql) m.delIds(tableName, sql)
m.mutex.Unlock()
} }
func (m *LRUCacher) delBean(tableName string, id string) { func (m *LRUCacher) delBean(tableName string, id string) {
@@ -261,8 +251,8 @@ func (m *LRUCacher) delBean(tableName string, id string) {
// DelBean deletes beans in some table // DelBean deletes beans in some table
func (m *LRUCacher) DelBean(tableName string, id string) { func (m *LRUCacher) DelBean(tableName string, id string) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock()
m.delBean(tableName, id) m.delBean(tableName, id)
m.mutex.Unlock()
} }
type idNode struct { type idNode struct {
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017 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.
// +build go1.8
package xorm
import "context"
// PingContext tests if database is alive
func (engine *Engine) PingContext(ctx context.Context) error {
session := engine.NewSession()
defer session.Close()
return session.PingContext(ctx)
}
// PingContext test if database is ok
func (session *Session) PingContext(ctx context.Context) error {
if session.isAutoClose {
defer session.Close()
}
session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName())
return session.DB().PingContext(ctx)
}
+102 -3
View File
@@ -209,10 +209,10 @@ func convertAssign(dest, src interface{}) error {
if src == nil { if src == nil {
dv.Set(reflect.Zero(dv.Type())) dv.Set(reflect.Zero(dv.Type()))
return nil return nil
} else {
dv.Set(reflect.New(dv.Type().Elem()))
return convertAssign(dv.Interface(), src)
} }
dv.Set(reflect.New(dv.Type().Elem()))
return convertAssign(dv.Interface(), src)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s := asString(src) s := asString(src)
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
@@ -247,3 +247,102 @@ func convertAssign(dest, src interface{}) error {
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest)
} }
func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) {
switch tp.Kind() {
case reflect.Int64:
return vv.Int(), nil
case reflect.Int:
return int(vv.Int()), nil
case reflect.Int32:
return int32(vv.Int()), nil
case reflect.Int16:
return int16(vv.Int()), nil
case reflect.Int8:
return int8(vv.Int()), nil
case reflect.Uint64:
return vv.Uint(), nil
case reflect.Uint:
return uint(vv.Uint()), nil
case reflect.Uint32:
return uint32(vv.Uint()), nil
case reflect.Uint16:
return uint16(vv.Uint()), nil
case reflect.Uint8:
return uint8(vv.Uint()), nil
case reflect.String:
return vv.String(), nil
case reflect.Slice:
if tp.Elem().Kind() == reflect.Uint8 {
v, err := strconv.ParseInt(string(vv.Interface().([]byte)), 10, 64)
if err != nil {
return nil, err
}
return v, nil
}
}
return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv)
}
func convertFloat(v interface{}) (float64, error) {
switch v.(type) {
case float32:
return float64(v.(float32)), nil
case float64:
return v.(float64), nil
case string:
i, err := strconv.ParseFloat(v.(string), 64)
if err != nil {
return 0, err
}
return i, nil
case []byte:
i, err := strconv.ParseFloat(string(v.([]byte)), 64)
if err != nil {
return 0, err
}
return i, nil
}
return 0, fmt.Errorf("unsupported type: %v", v)
}
func convertInt(v interface{}) (int64, error) {
switch v.(type) {
case int:
return int64(v.(int)), nil
case int8:
return int64(v.(int8)), nil
case int16:
return int64(v.(int16)), nil
case int32:
return int64(v.(int32)), nil
case int64:
return v.(int64), nil
case []byte:
i, err := strconv.ParseInt(string(v.([]byte)), 10, 64)
if err != nil {
return 0, err
}
return i, nil
case string:
i, err := strconv.ParseInt(v.(string), 10, 64)
if err != nil {
return 0, err
}
return i, nil
}
return 0, fmt.Errorf("unsupported type: %v", v)
}
func asBool(bs []byte) (bool, error) {
if len(bs) == 0 {
return false, nil
}
if bs[0] == 0x00 {
return false, nil
} else if bs[0] == 0x01 {
return true, nil
}
return strconv.ParseBool(string(bs))
}
+21 -10
View File
@@ -215,10 +215,10 @@ func (db *mssql) SqlType(c *core.Column) string {
var res string var res string
switch t := c.SQLType.Name; t { switch t := c.SQLType.Name; t {
case core.Bool: case core.Bool:
res = core.TinyInt res = core.Bit
if c.Default == "true" { if strings.EqualFold(c.Default, "true") {
c.Default = "1" c.Default = "1"
} else if c.Default == "false" { } else {
c.Default = "0" c.Default = "0"
} }
case core.Serial: case core.Serial:
@@ -250,6 +250,9 @@ func (db *mssql) SqlType(c *core.Column) string {
case core.Uuid: case core.Uuid:
res = core.Varchar res = core.Varchar
c.Length = 40 c.Length = 40
case core.TinyInt:
res = core.TinyInt
c.Length = 0
default: default:
res = t res = t
} }
@@ -335,9 +338,15 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) {
func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
args := []interface{}{} args := []interface{}{}
s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable,
replace(replace(isnull(c.text,''),'(',''),')','') as vdefault replace(replace(isnull(c.text,''),'(',''),')','') as vdefault,
from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id ISNULL(i.is_primary_key, 0)
left join sys.syscomments c on a.default_object_id=c.id from sys.columns a
left join sys.types b on a.user_type_id=b.user_type_id
left join sys.syscomments c on a.default_object_id=c.id
LEFT OUTER JOIN
sys.index_columns ic ON ic.object_id = a.object_id AND ic.column_id = a.column_id
LEFT OUTER JOIN
sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
where a.object_id=object_id('` + tableName + `')` where a.object_id=object_id('` + tableName + `')`
db.LogSQL(s, args) db.LogSQL(s, args)
@@ -352,8 +361,8 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column
for rows.Next() { for rows.Next() {
var name, ctype, vdefault string var name, ctype, vdefault string
var maxLen, precision, scale int var maxLen, precision, scale int
var nullable bool var nullable, isPK bool
err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &vdefault) err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &vdefault, &isPK)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -363,6 +372,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column
col.Name = strings.Trim(name, "` ") col.Name = strings.Trim(name, "` ")
col.Nullable = nullable col.Nullable = nullable
col.Default = vdefault col.Default = vdefault
col.IsPrimaryKey = isPK
ct := strings.ToUpper(ctype) ct := strings.ToUpper(ctype)
if ct == "DECIMAL" { if ct == "DECIMAL" {
col.Length = precision col.Length = precision
@@ -468,9 +478,10 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
} }
colName = strings.Trim(colName, "` ") colName = strings.Trim(colName, "` ")
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
indexName = indexName[5+len(tableName):] indexName = indexName[5+len(tableName):]
isRegular = true
} }
var index *core.Index var index *core.Index
@@ -479,6 +490,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
index = new(core.Index) index = new(core.Index)
index.Type = indexType index.Type = indexType
index.Name = indexName index.Name = indexName
index.IsRegular = isRegular
indexes[indexName] = index indexes[indexName] = index
} }
index.AddColumn(colName) index.AddColumn(colName)
@@ -534,7 +546,6 @@ type odbcDriver struct {
func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
kv := strings.Split(dataSourceName, ";") kv := strings.Split(dataSourceName, ";")
var dbName string var dbName string
for _, c := range kv { for _, c := range kv {
vv := strings.Split(strings.TrimSpace(c), "=") vv := strings.Split(strings.TrimSpace(c), "=")
if len(vv) == 2 { if len(vv) == 2 {
+8 -6
View File
@@ -299,7 +299,7 @@ func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) {
func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
args := []interface{}{db.DbName, tableName} args := []interface{}{db.DbName, tableName}
s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," +
" `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
db.LogSQL(s, args) db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...) rows, err := db.DB().Query(s, args...)
@@ -314,13 +314,14 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column
col := new(core.Column) col := new(core.Column)
col.Indexes = make(map[string]int) col.Indexes = make(map[string]int)
var columnName, isNullable, colType, colKey, extra string var columnName, isNullable, colType, colKey, extra, comment string
var colDefault *string var colDefault *string
err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra) err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra, &comment)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
col.Name = strings.Trim(columnName, "` ") col.Name = strings.Trim(columnName, "` ")
col.Comment = comment
if "YES" == isNullable { if "YES" == isNullable {
col.Nullable = true col.Nullable = true
} }
@@ -407,7 +408,7 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column
func (db *mysql) GetTables() ([]*core.Table, error) { func (db *mysql) GetTables() ([]*core.Table, error) {
args := []interface{}{db.DbName} args := []interface{}{db.DbName}
s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT` from " + s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " +
"`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')" "`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')"
db.LogSQL(s, args) db.LogSQL(s, args)
@@ -420,14 +421,15 @@ func (db *mysql) GetTables() ([]*core.Table, error) {
tables := make([]*core.Table, 0) tables := make([]*core.Table, 0)
for rows.Next() { for rows.Next() {
table := core.NewEmptyTable() table := core.NewEmptyTable()
var name, engine, tableRows string var name, engine, tableRows, comment string
var autoIncr *string var autoIncr *string
err = rows.Scan(&name, &engine, &tableRows, &autoIncr) err = rows.Scan(&name, &engine, &tableRows, &autoIncr, &comment)
if err != nil { if err != nil {
return nil, err return nil, err
} }
table.Name = name table.Name = name
table.Comment = comment
table.StoreEngine = engine table.StoreEngine = engine
tables = append(tables, table) tables = append(tables, table)
} }
+7
View File
@@ -824,6 +824,12 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
indexName = strings.Trim(indexName, `" `) indexName = strings.Trim(indexName, `" `)
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
indexName = indexName[5+len(tableName):]
isRegular = true
}
if uniqueness == "UNIQUE" { if uniqueness == "UNIQUE" {
indexType = core.UniqueType indexType = core.UniqueType
} else { } else {
@@ -836,6 +842,7 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
index = new(core.Index) index = new(core.Index)
index.Type = indexType index.Type = indexType
index.Name = indexName index.Name = indexName
index.IsRegular = isRegular
indexes[indexName] = index indexes[indexName] = index
} }
index.AddColumn(colName) index.AddColumn(colName)
+25 -48
View File
@@ -8,7 +8,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings" "strings"
@@ -781,6 +780,9 @@ func (db *postgres) SqlType(c *core.Column) string {
case core.TinyInt: case core.TinyInt:
res = core.SmallInt res = core.SmallInt
return res return res
case core.Bit:
res = core.Boolean
return res
case core.MediumInt, core.Int, core.Integer: case core.MediumInt, core.Int, core.Integer:
if c.IsAutoIncrement { if c.IsAutoIncrement {
return core.Serial return core.Serial
@@ -1078,9 +1080,10 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error)
} }
cs := strings.Split(indexdef, "(") cs := strings.Split(indexdef, "(")
colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") colNames = strings.Split(cs[1][0:len(cs[1])-1], ",")
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
newIdxName := indexName[5+len(tableName):] newIdxName := indexName[5+len(tableName):]
isRegular = true
if newIdxName != "" { if newIdxName != "" {
indexName = newIdxName indexName = newIdxName
} }
@@ -1090,6 +1093,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error)
for _, colName := range colNames { for _, colName := range colNames {
index.Cols = append(index.Cols, strings.Trim(colName, `" `)) index.Cols = append(index.Cols, strings.Trim(colName, `" `))
} }
index.IsRegular = isRegular
indexes[index.Name] = index indexes[index.Name] = index
} }
return indexes, nil return indexes, nil
@@ -1112,10 +1116,6 @@ func (vs values) Get(k string) (v string) {
return vs[k] return vs[k]
} }
func errorf(s string, args ...interface{}) {
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
}
func parseURL(connstr string) (string, error) { func parseURL(connstr string) (string, error) {
u, err := url.Parse(connstr) u, err := url.Parse(connstr)
if err != nil { if err != nil {
@@ -1126,46 +1126,18 @@ func parseURL(connstr string) (string, error) {
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme) return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
} }
var kvs []string
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
accrue := func(k, v string) {
if v != "" {
kvs = append(kvs, k+"="+escaper.Replace(v))
}
}
if u.User != nil {
v := u.User.Username()
accrue("user", v)
v, _ = u.User.Password()
accrue("password", v)
}
i := strings.Index(u.Host, ":")
if i < 0 {
accrue("host", u.Host)
} else {
accrue("host", u.Host[:i])
accrue("port", u.Host[i+1:])
}
if u.Path != "" { if u.Path != "" {
accrue("dbname", u.Path[1:]) return escaper.Replace(u.Path[1:]), nil
} }
q := u.Query() return "", nil
for k := range q {
accrue(k, q.Get(k))
}
sort.Strings(kvs) // Makes testing easier (not a performance concern)
return strings.Join(kvs, " "), nil
} }
func parseOpts(name string, o values) { func parseOpts(name string, o values) error {
if len(name) == 0 { if len(name) == 0 {
return return fmt.Errorf("invalid options: %s", name)
} }
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
@@ -1174,31 +1146,36 @@ func parseOpts(name string, o values) {
for _, p := range ps { for _, p := range ps {
kv := strings.Split(p, "=") kv := strings.Split(p, "=")
if len(kv) < 2 { if len(kv) < 2 {
errorf("invalid option: %q", p) return fmt.Errorf("invalid option: %q", p)
} }
o.Set(kv[0], kv[1]) o.Set(kv[0], kv[1])
} }
return nil
} }
func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
db := &core.Uri{DbType: core.POSTGRES} db := &core.Uri{DbType: core.POSTGRES}
o := make(values)
var err error var err error
if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") { if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") {
dataSourceName, err = parseURL(dataSourceName) db.DbName, err = parseURL(dataSourceName)
if err != nil {
return nil, err
}
} else {
o := make(values)
err = parseOpts(dataSourceName, o)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
parseOpts(dataSourceName, o)
db.DbName = o.Get("dbname") db.DbName = o.Get("dbname")
}
if db.DbName == "" { if db.DbName == "" {
return nil, errors.New("dbname is empty") return nil, errors.New("dbname is empty")
} }
/*db.Schema = o.Get("schema")
if len(db.Schema) == 0 {
db.Schema = "public"
}*/
return db, nil return db, nil
} }
+17 -4
View File
@@ -14,10 +14,6 @@ import (
"github.com/go-xorm/core" "github.com/go-xorm/core"
) )
// func init() {
// RegisterDialect("sqlite3", &sqlite3{})
// }
var ( var (
sqlite3ReservedWords = map[string]bool{ sqlite3ReservedWords = map[string]bool{
"ABORT": true, "ABORT": true,
@@ -310,11 +306,25 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu
for _, colStr := range colCreates { for _, colStr := range colCreates {
reg = regexp.MustCompile(`,\s`) reg = regexp.MustCompile(`,\s`)
colStr = reg.ReplaceAllString(colStr, ",") colStr = reg.ReplaceAllString(colStr, ",")
if strings.HasPrefix(strings.TrimSpace(colStr), "PRIMARY KEY") {
parts := strings.Split(strings.TrimSpace(colStr), "(")
if len(parts) == 2 {
pkCols := strings.Split(strings.TrimRight(strings.TrimSpace(parts[1]), ")"), ",")
for _, pk := range pkCols {
if col, ok := cols[strings.Trim(strings.TrimSpace(pk), "`")]; ok {
col.IsPrimaryKey = true
}
}
}
continue
}
fields := strings.Fields(strings.TrimSpace(colStr)) fields := strings.Fields(strings.TrimSpace(colStr))
col := new(core.Column) col := new(core.Column)
col.Indexes = make(map[string]int) col.Indexes = make(map[string]int)
col.Nullable = true col.Nullable = true
col.DefaultIsEmpty = true col.DefaultIsEmpty = true
for idx, field := range fields { for idx, field := range fields {
if idx == 0 { if idx == 0 {
col.Name = strings.Trim(strings.Trim(field, "`[] "), `"`) col.Name = strings.Trim(strings.Trim(field, "`[] "), `"`)
@@ -405,8 +415,10 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error)
} }
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
index.Name = indexName[5+len(tableName):] index.Name = indexName[5+len(tableName):]
isRegular = true
} else { } else {
index.Name = indexName index.Name = indexName
} }
@@ -425,6 +437,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error)
for _, col := range colIndexes { for _, col := range colIndexes {
index.Cols = append(index.Cols, strings.Trim(col, "` []")) index.Cols = append(index.Cols, strings.Trim(col, "` []"))
} }
index.IsRegular = isRegular
indexes[index.Name] = index indexes[index.Name] = index
} }
+10 -3
View File
@@ -8,7 +8,7 @@ Package xorm is a simple and powerful ORM for Go.
Installation Installation
Make sure you have installed Go 1.1+ and then: Make sure you have installed Go 1.6+ and then:
go get github.com/go-xorm/xorm go get github.com/go-xorm/xorm
@@ -51,11 +51,15 @@ There are 8 major ORM methods and many helpful methods to use to operate databas
// INSERT INTO struct1 () values () // INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values (),(),() // INSERT INTO struct2 () values (),(),()
2. Query one record from database 2. Query one record or one variable from database
has, err := engine.Get(&user) has, err := engine.Get(&user)
// SELECT * FROM user LIMIT 1 // SELECT * FROM user LIMIT 1
var id int64
has, err := engine.Table("user").Where("name = ?", name).Get(&id)
// SELECT id FROM user WHERE name = ? LIMIT 1
3. Query multiple records from database 3. Query multiple records from database
var sliceOfStructs []Struct var sliceOfStructs []Struct
@@ -86,7 +90,7 @@ another is Rows
5. Update one or more records 5. Update one or more records
affected, err := engine.Id(...).Update(&user) affected, err := engine.ID(...).Update(&user)
// UPDATE user SET ... // UPDATE user SET ...
6. Delete one or more records, Delete MUST has condition 6. Delete one or more records, Delete MUST has condition
@@ -99,6 +103,9 @@ another is Rows
counts, err := engine.Count(&user) counts, err := engine.Count(&user)
// SELECT count(*) AS total FROM user // SELECT count(*) AS total FROM user
counts, err := engine.SQL("select count(*) FROM user").Count()
// select count(*) FROM user
8. Sum records 8. Sum records
sumFloat64, err := engine.Sum(&user, "id") sumFloat64, err := engine.Sum(&user, "id")
+284 -210
View File
@@ -19,6 +19,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/go-xorm/builder"
"github.com/go-xorm/core" "github.com/go-xorm/core"
) )
@@ -40,12 +41,29 @@ type Engine struct {
showExecTime bool showExecTime bool
logger core.ILogger logger core.ILogger
TZLocation *time.Location TZLocation *time.Location // The timezone of the application
DatabaseTZ *time.Location // The timezone of the database DatabaseTZ *time.Location // The timezone of the database
disableGlobalCache bool disableGlobalCache bool
tagHandlers map[string]tagHandler tagHandlers map[string]tagHandler
engineGroup *EngineGroup
}
// BufferSize sets buffer size for iterate
func (engine *Engine) BufferSize(size int) *Session {
session := engine.NewSession()
session.isAutoClose = true
return session.BufferSize(size)
}
// CondDeleted returns the conditions whether a record is soft deleted.
func (engine *Engine) CondDeleted(colName string) builder.Cond {
if engine.dialect.DBType() == core.MSSQL {
return builder.IsNull{colName}
}
return builder.IsNull{colName}.Or(builder.Eq{colName: zeroTime1})
} }
// ShowSQL show SQL statement or not on logger if log level is great than INFO // ShowSQL show SQL statement or not on logger if log level is great than INFO
@@ -78,6 +96,11 @@ func (engine *Engine) SetLogger(logger core.ILogger) {
engine.dialect.SetLogger(logger) engine.dialect.SetLogger(logger)
} }
// SetLogLevel sets the logger level
func (engine *Engine) SetLogLevel(level core.LogLevel) {
engine.logger.SetLevel(level)
}
// SetDisableGlobalCache disable global cache or not // SetDisableGlobalCache disable global cache or not
func (engine *Engine) SetDisableGlobalCache(disable bool) { func (engine *Engine) SetDisableGlobalCache(disable bool) {
if engine.disableGlobalCache != disable { if engine.disableGlobalCache != disable {
@@ -143,7 +166,6 @@ func (engine *Engine) Quote(value string) string {
// QuoteTo quotes string and writes into the buffer // QuoteTo quotes string and writes into the buffer
func (engine *Engine) QuoteTo(buf *bytes.Buffer, value string) { func (engine *Engine) QuoteTo(buf *bytes.Buffer, value string) {
if buf == nil { if buf == nil {
return return
} }
@@ -169,7 +191,7 @@ func (engine *Engine) quote(sql string) string {
return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr() return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr()
} }
// SqlType will be depracated, please use SQLType instead // SqlType will be deprecated, please use SQLType instead
// //
// Deprecated: use SQLType instead // Deprecated: use SQLType instead
func (engine *Engine) SqlType(c *core.Column) string { func (engine *Engine) SqlType(c *core.Column) string {
@@ -201,26 +223,36 @@ func (engine *Engine) SetDefaultCacher(cacher core.Cacher) {
engine.Cacher = cacher engine.Cacher = cacher
} }
// GetDefaultCacher returns the default cacher
func (engine *Engine) GetDefaultCacher() core.Cacher {
return engine.Cacher
}
// NoCache If you has set default cacher, and you want temporilly stop use cache, // NoCache If you has set default cacher, and you want temporilly stop use cache,
// you can use NoCache() // you can use NoCache()
func (engine *Engine) NoCache() *Session { func (engine *Engine) NoCache() *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.NoCache() return session.NoCache()
} }
// NoCascade If you do not want to auto cascade load object // NoCascade If you do not want to auto cascade load object
func (engine *Engine) NoCascade() *Session { func (engine *Engine) NoCascade() *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.NoCascade() return session.NoCascade()
} }
// MapCacher Set a table use a special cacher // MapCacher Set a table use a special cacher
func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) { func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error {
v := rValue(bean) v := rValue(bean)
tb := engine.autoMapType(v) tb, err := engine.autoMapType(v)
if err != nil {
return err
}
tb.Cacher = cacher tb.Cacher = cacher
return nil
} }
// NewDB provides an interface to operate database directly // NewDB provides an interface to operate database directly
@@ -240,7 +272,7 @@ func (engine *Engine) Dialect() core.Dialect {
// NewSession New a session // NewSession New a session
func (engine *Engine) NewSession() *Session { func (engine *Engine) NewSession() *Session {
session := &Session{Engine: engine} session := &Session{engine: engine}
session.Init() session.Init()
return session return session
} }
@@ -254,7 +286,6 @@ func (engine *Engine) Close() error {
func (engine *Engine) Ping() error { func (engine *Engine) Ping() error {
session := engine.NewSession() session := engine.NewSession()
defer session.Close() defer session.Close()
engine.logger.Infof("PING DATABASE %v", engine.DriverName())
return session.Ping() return session.Ping()
} }
@@ -262,43 +293,13 @@ func (engine *Engine) Ping() error {
func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) { func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) {
if engine.showSQL && !engine.showExecTime { if engine.showSQL && !engine.showExecTime {
if len(sqlArgs) > 0 { if len(sqlArgs) > 0 {
engine.logger.Infof("[SQL] %v %v", sqlStr, sqlArgs) engine.logger.Infof("[SQL] %v %#v", sqlStr, sqlArgs)
} else { } else {
engine.logger.Infof("[SQL] %v", sqlStr) engine.logger.Infof("[SQL] %v", sqlStr)
} }
} }
} }
func (engine *Engine) logSQLQueryTime(sqlStr string, args []interface{}, executionBlock func() (*core.Stmt, *core.Rows, error)) (*core.Stmt, *core.Rows, error) {
if engine.showSQL && engine.showExecTime {
b4ExecTime := time.Now()
stmt, res, err := executionBlock()
execDuration := time.Since(b4ExecTime)
if len(args) > 0 {
engine.logger.Infof("[SQL] %s %v - took: %v", sqlStr, args, execDuration)
} else {
engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration)
}
return stmt, res, err
}
return executionBlock()
}
func (engine *Engine) logSQLExecutionTime(sqlStr string, args []interface{}, executionBlock func() (sql.Result, error)) (sql.Result, error) {
if engine.showSQL && engine.showExecTime {
b4ExecTime := time.Now()
res, err := executionBlock()
execDuration := time.Since(b4ExecTime)
if len(args) > 0 {
engine.logger.Infof("[sql] %s [args] %v - took: %v", sqlStr, args, execDuration)
} else {
engine.logger.Infof("[sql] %s - took: %v", sqlStr, execDuration)
}
return res, err
}
return executionBlock()
}
// Sql provides raw sql input parameter. When you have a complex SQL statement // Sql provides raw sql input parameter. When you have a complex SQL statement
// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. // and cannot use Where, Id, In and etc. Methods to describe, you can use SQL.
// //
@@ -315,7 +316,7 @@ func (engine *Engine) Sql(querystring string, args ...interface{}) *Session {
// This code will execute "select * from user" and set the records to users // This code will execute "select * from user" and set the records to users
func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session { func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.SQL(query, args...) return session.SQL(query, args...)
} }
@@ -324,14 +325,14 @@ func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session {
// invoked. Call NoAutoTime if you dont' want to fill automatically. // invoked. Call NoAutoTime if you dont' want to fill automatically.
func (engine *Engine) NoAutoTime() *Session { func (engine *Engine) NoAutoTime() *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.NoAutoTime() return session.NoAutoTime()
} }
// NoAutoCondition disable auto generate Where condition from bean or not // NoAutoCondition disable auto generate Where condition from bean or not
func (engine *Engine) NoAutoCondition(no ...bool) *Session { func (engine *Engine) NoAutoCondition(no ...bool) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.NoAutoCondition(no...) return session.NoAutoCondition(no...)
} }
@@ -565,56 +566,56 @@ func (engine *Engine) tbName(v reflect.Value) string {
// Cascade use cascade or not // Cascade use cascade or not
func (engine *Engine) Cascade(trueOrFalse ...bool) *Session { func (engine *Engine) Cascade(trueOrFalse ...bool) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Cascade(trueOrFalse...) return session.Cascade(trueOrFalse...)
} }
// Where method provide a condition query // Where method provide a condition query
func (engine *Engine) Where(query interface{}, args ...interface{}) *Session { func (engine *Engine) Where(query interface{}, args ...interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Where(query, args...) return session.Where(query, args...)
} }
// Id will be depracated, please use ID instead // Id will be deprecated, please use ID instead
func (engine *Engine) Id(id interface{}) *Session { func (engine *Engine) Id(id interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Id(id) return session.Id(id)
} }
// ID method provoide a condition as (id) = ? // ID method provoide a condition as (id) = ?
func (engine *Engine) ID(id interface{}) *Session { func (engine *Engine) ID(id interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.ID(id) return session.ID(id)
} }
// Before apply before Processor, affected bean is passed to closure arg // Before apply before Processor, affected bean is passed to closure arg
func (engine *Engine) Before(closures func(interface{})) *Session { func (engine *Engine) Before(closures func(interface{})) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Before(closures) return session.Before(closures)
} }
// After apply after insert Processor, affected bean is passed to closure arg // After apply after insert Processor, affected bean is passed to closure arg
func (engine *Engine) After(closures func(interface{})) *Session { func (engine *Engine) After(closures func(interface{})) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.After(closures) return session.After(closures)
} }
// Charset set charset when create table, only support mysql now // Charset set charset when create table, only support mysql now
func (engine *Engine) Charset(charset string) *Session { func (engine *Engine) Charset(charset string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Charset(charset) return session.Charset(charset)
} }
// StoreEngine set store engine when create table, only support mysql now // StoreEngine set store engine when create table, only support mysql now
func (engine *Engine) StoreEngine(storeEngine string) *Session { func (engine *Engine) StoreEngine(storeEngine string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.StoreEngine(storeEngine) return session.StoreEngine(storeEngine)
} }
@@ -623,35 +624,35 @@ func (engine *Engine) StoreEngine(storeEngine string) *Session {
// but distinct will not provide id // but distinct will not provide id
func (engine *Engine) Distinct(columns ...string) *Session { func (engine *Engine) Distinct(columns ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Distinct(columns...) return session.Distinct(columns...)
} }
// Select customerize your select columns or contents // Select customerize your select columns or contents
func (engine *Engine) Select(str string) *Session { func (engine *Engine) Select(str string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Select(str) return session.Select(str)
} }
// Cols only use the parameters as select or update columns // Cols only use the parameters as select or update columns
func (engine *Engine) Cols(columns ...string) *Session { func (engine *Engine) Cols(columns ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Cols(columns...) return session.Cols(columns...)
} }
// AllCols indicates that all columns should be use // AllCols indicates that all columns should be use
func (engine *Engine) AllCols() *Session { func (engine *Engine) AllCols() *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.AllCols() return session.AllCols()
} }
// MustCols specify some columns must use even if they are empty // MustCols specify some columns must use even if they are empty
func (engine *Engine) MustCols(columns ...string) *Session { func (engine *Engine) MustCols(columns ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.MustCols(columns...) return session.MustCols(columns...)
} }
@@ -662,77 +663,84 @@ func (engine *Engine) MustCols(columns ...string) *Session {
// it will use parameters's columns // it will use parameters's columns
func (engine *Engine) UseBool(columns ...string) *Session { func (engine *Engine) UseBool(columns ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.UseBool(columns...) return session.UseBool(columns...)
} }
// Omit only not use the parameters as select or update columns // Omit only not use the parameters as select or update columns
func (engine *Engine) Omit(columns ...string) *Session { func (engine *Engine) Omit(columns ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Omit(columns...) return session.Omit(columns...)
} }
// Nullable set null when column is zero-value and nullable for update // Nullable set null when column is zero-value and nullable for update
func (engine *Engine) Nullable(columns ...string) *Session { func (engine *Engine) Nullable(columns ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Nullable(columns...) return session.Nullable(columns...)
} }
// In will generate "column IN (?, ?)" // In will generate "column IN (?, ?)"
func (engine *Engine) In(column string, args ...interface{}) *Session { func (engine *Engine) In(column string, args ...interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.In(column, args...) return session.In(column, args...)
} }
// NotIn will generate "column NOT IN (?, ?)"
func (engine *Engine) NotIn(column string, args ...interface{}) *Session {
session := engine.NewSession()
session.isAutoClose = true
return session.NotIn(column, args...)
}
// Incr provides a update string like "column = column + ?" // Incr provides a update string like "column = column + ?"
func (engine *Engine) Incr(column string, arg ...interface{}) *Session { func (engine *Engine) Incr(column string, arg ...interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Incr(column, arg...) return session.Incr(column, arg...)
} }
// Decr provides a update string like "column = column - ?" // Decr provides a update string like "column = column - ?"
func (engine *Engine) Decr(column string, arg ...interface{}) *Session { func (engine *Engine) Decr(column string, arg ...interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Decr(column, arg...) return session.Decr(column, arg...)
} }
// SetExpr provides a update string like "column = {expression}" // SetExpr provides a update string like "column = {expression}"
func (engine *Engine) SetExpr(column string, expression string) *Session { func (engine *Engine) SetExpr(column string, expression string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.SetExpr(column, expression) return session.SetExpr(column, expression)
} }
// Table temporarily change the Get, Find, Update's table // Table temporarily change the Get, Find, Update's table
func (engine *Engine) Table(tableNameOrBean interface{}) *Session { func (engine *Engine) Table(tableNameOrBean interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Table(tableNameOrBean) return session.Table(tableNameOrBean)
} }
// Alias set the table alias // Alias set the table alias
func (engine *Engine) Alias(alias string) *Session { func (engine *Engine) Alias(alias string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Alias(alias) return session.Alias(alias)
} }
// Limit will generate "LIMIT start, limit" // Limit will generate "LIMIT start, limit"
func (engine *Engine) Limit(limit int, start ...int) *Session { func (engine *Engine) Limit(limit int, start ...int) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Limit(limit, start...) return session.Limit(limit, start...)
} }
// Desc will generate "ORDER BY column1 DESC, column2 DESC" // Desc will generate "ORDER BY column1 DESC, column2 DESC"
func (engine *Engine) Desc(colNames ...string) *Session { func (engine *Engine) Desc(colNames ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Desc(colNames...) return session.Desc(colNames...)
} }
@@ -744,39 +752,53 @@ func (engine *Engine) Desc(colNames ...string) *Session {
// //
func (engine *Engine) Asc(colNames ...string) *Session { func (engine *Engine) Asc(colNames ...string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Asc(colNames...) return session.Asc(colNames...)
} }
// OrderBy will generate "ORDER BY order" // OrderBy will generate "ORDER BY order"
func (engine *Engine) OrderBy(order string) *Session { func (engine *Engine) OrderBy(order string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.OrderBy(order) return session.OrderBy(order)
} }
// Prepare enables prepare statement
func (engine *Engine) Prepare() *Session {
session := engine.NewSession()
session.isAutoClose = true
return session.Prepare()
}
// Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN // Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Join(joinOperator, tablename, condition, args...) return session.Join(joinOperator, tablename, condition, args...)
} }
// GroupBy generate group by statement // GroupBy generate group by statement
func (engine *Engine) GroupBy(keys string) *Session { func (engine *Engine) GroupBy(keys string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.GroupBy(keys) return session.GroupBy(keys)
} }
// Having generate having statement // Having generate having statement
func (engine *Engine) Having(conditions string) *Session { func (engine *Engine) Having(conditions string) *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Having(conditions) return session.Having(conditions)
} }
func (engine *Engine) autoMapType(v reflect.Value) *core.Table { // UnMapType removes the datbase mapper of a type
func (engine *Engine) UnMapType(t reflect.Type) {
engine.mutex.Lock()
defer engine.mutex.Unlock()
delete(engine.Tables, t)
}
func (engine *Engine) autoMapType(v reflect.Value) (*core.Table, error) {
t := v.Type() t := v.Type()
engine.mutex.Lock() engine.mutex.Lock()
defer engine.mutex.Unlock() defer engine.mutex.Unlock()
@@ -785,24 +807,23 @@ func (engine *Engine) autoMapType(v reflect.Value) *core.Table {
var err error var err error
table, err = engine.mapType(v) table, err = engine.mapType(v)
if err != nil { if err != nil {
engine.logger.Error(err) return nil, err
} else { }
engine.Tables[t] = table
if engine.Cacher != nil { engine.Tables[t] = table
if v.CanAddr() { if engine.Cacher != nil {
engine.GobRegister(v.Addr().Interface()) if v.CanAddr() {
} else { engine.GobRegister(v.Addr().Interface())
engine.GobRegister(v.Interface()) } else {
} engine.GobRegister(v.Interface())
} }
} }
} }
return table return table, nil
} }
// GobRegister register one struct to gob for cache use // GobRegister register one struct to gob for cache use
func (engine *Engine) GobRegister(v interface{}) *Engine { func (engine *Engine) GobRegister(v interface{}) *Engine {
//fmt.Printf("Type: %[1]T => Data: %[1]#v\n", v)
gob.Register(v) gob.Register(v)
return engine return engine
} }
@@ -813,10 +834,19 @@ type Table struct {
Name string Name string
} }
// IsValid if table is valid
func (t *Table) IsValid() bool {
return t.Table != nil && len(t.Name) > 0
}
// TableInfo get table info according to bean's content // TableInfo get table info according to bean's content
func (engine *Engine) TableInfo(bean interface{}) *Table { func (engine *Engine) TableInfo(bean interface{}) *Table {
v := rValue(bean) v := rValue(bean)
return &Table{engine.autoMapType(v), engine.tbName(v)} tb, err := engine.autoMapType(v)
if err != nil {
engine.logger.Error(err)
}
return &Table{tb, engine.tbName(v)}
} }
func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) { func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) {
@@ -911,6 +941,7 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
k := strings.ToUpper(key) k := strings.ToUpper(key)
ctx.tagName = k ctx.tagName = k
ctx.params = []string{}
pStart := strings.Index(k, "(") pStart := strings.Index(k, "(")
if pStart == 0 { if pStart == 0 {
@@ -918,18 +949,18 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
} }
if pStart > -1 { if pStart > -1 {
if !strings.HasSuffix(k, ")") { if !strings.HasSuffix(k, ")") {
return nil, errors.New("cannot match ) charactor") return nil, fmt.Errorf("field %s tag %s cannot match ) charactor", col.FieldName, key)
} }
ctx.tagName = k[:pStart] ctx.tagName = k[:pStart]
ctx.params = strings.Split(k[pStart+1:len(k)-1], ",") ctx.params = strings.Split(key[pStart+1:len(k)-1], ",")
} }
if j > 0 { if j > 0 {
ctx.preTag = strings.ToUpper(tags[j-1]) ctx.preTag = strings.ToUpper(tags[j-1])
} }
if j < len(tags)-1 { if j < len(tags)-1 {
ctx.nextTag = strings.ToUpper(tags[j+1]) ctx.nextTag = tags[j+1]
} else { } else {
ctx.nextTag = "" ctx.nextTag = ""
} }
@@ -993,6 +1024,10 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name),
t.Field(i).Name, sqlType, sqlType.DefaultLength, t.Field(i).Name, sqlType, sqlType.DefaultLength,
sqlType.DefaultLength2, true) sqlType.DefaultLength2, true)
if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
idFieldColName = col.Name
}
} }
if col.IsAutoIncrement { if col.IsAutoIncrement {
col.Nullable = false col.Nullable = false
@@ -1000,9 +1035,6 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
table.AddColumn(col) table.AddColumn(col)
if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
idFieldColName = col.Name
}
} // end for } // end for
if idFieldColName != "" && len(table.PrimaryKeys) == 0 { if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
@@ -1066,21 +1098,54 @@ func (engine *Engine) IdOfV(rv reflect.Value) core.PK {
// IDOfV get id from one value of struct // IDOfV get id from one value of struct
func (engine *Engine) IDOfV(rv reflect.Value) core.PK { func (engine *Engine) IDOfV(rv reflect.Value) core.PK {
pk, err := engine.idOfV(rv)
if err != nil {
engine.logger.Error(err)
return nil
}
return pk
}
func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) {
v := reflect.Indirect(rv) v := reflect.Indirect(rv)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil {
return nil, err
}
pk := make([]interface{}, len(table.PrimaryKeys)) pk := make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() { for i, col := range table.PKColumns() {
var err error
pkField := v.FieldByName(col.FieldName) pkField := v.FieldByName(col.FieldName)
switch pkField.Kind() { switch pkField.Kind() {
case reflect.String: case reflect.String:
pk[i] = pkField.String() pk[i], err = engine.idTypeAssertion(col, pkField.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
pk[i] = pkField.Int() pk[i], err = engine.idTypeAssertion(col, strconv.FormatInt(pkField.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
pk[i] = pkField.Uint() // id of uint will be converted to int64
pk[i], err = engine.idTypeAssertion(col, strconv.FormatUint(pkField.Uint(), 10))
}
if err != nil {
return nil, err
} }
} }
return core.PK(pk) return core.PK(pk), nil
}
func (engine *Engine) idTypeAssertion(col *core.Column, sid string) (interface{}, error) {
if col.SQLType.IsNumeric() {
n, err := strconv.ParseInt(sid, 10, 64)
if err != nil {
return nil, err
}
return n, nil
} else if col.SQLType.IsText() {
return sid, nil
} else {
return nil, errors.New("not supported")
}
} }
// CreateIndexes create indexes // CreateIndexes create indexes
@@ -1101,13 +1166,6 @@ func (engine *Engine) getCacher2(table *core.Table) core.Cacher {
return table.Cacher return table.Cacher
} }
func (engine *Engine) getCacher(v reflect.Value) core.Cacher {
if table := engine.autoMapType(v); table != nil {
return table.Cacher
}
return engine.Cacher
}
// ClearCacheBean if enabled cache, clear the cache bean // ClearCacheBean if enabled cache, clear the cache bean
func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
v := rValue(bean) v := rValue(bean)
@@ -1116,7 +1174,10 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
return errors.New("error params") return errors.New("error params")
} }
tableName := engine.tbName(v) tableName := engine.tbName(v)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher cacher := table.Cacher
if cacher == nil { if cacher == nil {
cacher = engine.Cacher cacher = engine.Cacher
@@ -1137,7 +1198,11 @@ func (engine *Engine) ClearCache(beans ...interface{}) error {
return errors.New("error params") return errors.New("error params")
} }
tableName := engine.tbName(v) tableName := engine.tbName(v)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil {
return err
}
cacher := table.Cacher cacher := table.Cacher
if cacher == nil { if cacher == nil {
cacher = engine.Cacher cacher = engine.Cacher
@@ -1154,19 +1219,23 @@ func (engine *Engine) ClearCache(beans ...interface{}) error {
// table, column, index, unique. but will not delete or change anything. // table, column, index, unique. but will not delete or change anything.
// If you change some field, you should change the database manually. // If you change some field, you should change the database manually.
func (engine *Engine) Sync(beans ...interface{}) error { func (engine *Engine) Sync(beans ...interface{}) error {
session := engine.NewSession()
defer session.Close()
for _, bean := range beans { for _, bean := range beans {
v := rValue(bean) v := rValue(bean)
tableName := engine.tbName(v) tableName := engine.tbName(v)
table := engine.autoMapType(v) table, err := engine.autoMapType(v)
if err != nil {
return err
}
s := engine.NewSession() isExist, err := session.Table(bean).isTableExist(tableName)
defer s.Close()
isExist, err := s.Table(bean).isTableExist(tableName)
if err != nil { if err != nil {
return err return err
} }
if !isExist { if !isExist {
err = engine.CreateTables(bean) err = session.createTable(bean)
if err != nil { if err != nil {
return err return err
} }
@@ -1177,11 +1246,11 @@ func (engine *Engine) Sync(beans ...interface{}) error {
}*/ }*/
var isEmpty bool var isEmpty bool
if isEmpty { if isEmpty {
err = engine.DropTables(bean) err = session.dropTable(bean)
if err != nil { if err != nil {
return err return err
} }
err = engine.CreateTables(bean) err = session.createTable(bean)
if err != nil { if err != nil {
return err return err
} }
@@ -1192,9 +1261,9 @@ func (engine *Engine) Sync(beans ...interface{}) error {
return err return err
} }
if !isExist { if !isExist {
session := engine.NewSession() if err := session.statement.setRefValue(v); err != nil {
session.Statement.setRefValue(v) return err
defer session.Close() }
err = session.addColumn(col.Name) err = session.addColumn(col.Name)
if err != nil { if err != nil {
return err return err
@@ -1203,19 +1272,19 @@ func (engine *Engine) Sync(beans ...interface{}) error {
} }
for name, index := range table.Indexes { for name, index := range table.Indexes {
session := engine.NewSession() if err := session.statement.setRefValue(v); err != nil {
session.Statement.setRefValue(v) return err
defer session.Close() }
if index.Type == core.UniqueType { if index.Type == core.UniqueType {
//isExist, err := session.isIndexExist(table.Name, name, true)
isExist, err := session.isIndexExist2(tableName, index.Cols, true) isExist, err := session.isIndexExist2(tableName, index.Cols, true)
if err != nil { if err != nil {
return err return err
} }
if !isExist { if !isExist {
session := engine.NewSession() if err := session.statement.setRefValue(v); err != nil {
session.Statement.setRefValue(v) return err
defer session.Close() }
err = session.addUnique(tableName, name) err = session.addUnique(tableName, name)
if err != nil { if err != nil {
return err return err
@@ -1227,9 +1296,10 @@ func (engine *Engine) Sync(beans ...interface{}) error {
return err return err
} }
if !isExist { if !isExist {
session := engine.NewSession() if err := session.statement.setRefValue(v); err != nil {
session.Statement.setRefValue(v) return err
defer session.Close() }
err = session.addIndex(tableName, name) err = session.addIndex(tableName, name)
if err != nil { if err != nil {
return err return err
@@ -1251,35 +1321,6 @@ func (engine *Engine) Sync2(beans ...interface{}) error {
return s.Sync2(beans...) return s.Sync2(beans...)
} }
func (engine *Engine) unMap(beans ...interface{}) (e error) {
engine.mutex.Lock()
defer engine.mutex.Unlock()
for _, bean := range beans {
t := rType(bean)
if _, ok := engine.Tables[t]; ok {
delete(engine.Tables, t)
}
}
return
}
// Drop all mapped table
func (engine *Engine) dropAll() error {
session := engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
err = session.dropAll()
if err != nil {
session.Rollback()
return err
}
return session.Commit()
}
// CreateTables create tabls according bean // CreateTables create tabls according bean
func (engine *Engine) CreateTables(beans ...interface{}) error { func (engine *Engine) CreateTables(beans ...interface{}) error {
session := engine.NewSession() session := engine.NewSession()
@@ -1291,7 +1332,7 @@ func (engine *Engine) CreateTables(beans ...interface{}) error {
} }
for _, bean := range beans { for _, bean := range beans {
err = session.CreateTable(bean) err = session.createTable(bean)
if err != nil { if err != nil {
session.Rollback() session.Rollback()
return err return err
@@ -1311,7 +1352,7 @@ func (engine *Engine) DropTables(beans ...interface{}) error {
} }
for _, bean := range beans { for _, bean := range beans {
err = session.DropTable(bean) err = session.dropTable(bean)
if err != nil { if err != nil {
session.Rollback() session.Rollback()
return err return err
@@ -1320,10 +1361,11 @@ func (engine *Engine) DropTables(beans ...interface{}) error {
return session.Commit() return session.Commit()
} }
func (engine *Engine) createAll() error { // DropIndexes drop indexes of a table
func (engine *Engine) DropIndexes(bean interface{}) error {
session := engine.NewSession() session := engine.NewSession()
defer session.Close() defer session.Close()
return session.createAll() return session.DropIndexes(bean)
} }
// Exec raw sql // Exec raw sql
@@ -1334,10 +1376,24 @@ func (engine *Engine) Exec(sql string, args ...interface{}) (sql.Result, error)
} }
// Query a raw sql and return records as []map[string][]byte // Query a raw sql and return records as []map[string][]byte
func (engine *Engine) Query(sql string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { func (engine *Engine) Query(sqlorArgs ...interface{}) (resultsSlice []map[string][]byte, err error) {
session := engine.NewSession() session := engine.NewSession()
defer session.Close() defer session.Close()
return session.Query(sql, paramStr...) return session.Query(sqlorArgs...)
}
// QueryString runs a raw sql and return records as []map[string]string
func (engine *Engine) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) {
session := engine.NewSession()
defer session.Close()
return session.QueryString(sqlorArgs...)
}
// QueryInterface runs a raw sql and return records as []map[string]interface{}
func (engine *Engine) QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) {
session := engine.NewSession()
defer session.Close()
return session.QueryInterface(sqlorArgs...)
} }
// Insert one or more records // Insert one or more records
@@ -1381,6 +1437,13 @@ func (engine *Engine) Get(bean interface{}) (bool, error) {
return session.Get(bean) return session.Get(bean)
} }
// Exist returns true if the record exist otherwise return false
func (engine *Engine) Exist(bean ...interface{}) (bool, error) {
session := engine.NewSession()
defer session.Close()
return session.Exist(bean...)
}
// Find retrieve records from table, condiBeans's non-empty fields // Find retrieve records from table, condiBeans's non-empty fields
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct // are conditions. beans could be []Struct, []*Struct, map[int64]Struct
// map[int64]*Struct // map[int64]*Struct
@@ -1406,10 +1469,10 @@ func (engine *Engine) Rows(bean interface{}) (*Rows, error) {
} }
// Count counts the records. bean's non-empty fields are conditions. // Count counts the records. bean's non-empty fields are conditions.
func (engine *Engine) Count(bean interface{}) (int64, error) { func (engine *Engine) Count(bean ...interface{}) (int64, error) {
session := engine.NewSession() session := engine.NewSession()
defer session.Close() defer session.Close()
return session.Count(bean) return session.Count(bean...)
} }
// Sum sum the records by some column. bean's non-empty fields are conditions. // Sum sum the records by some column. bean's non-empty fields are conditions.
@@ -1419,6 +1482,13 @@ func (engine *Engine) Sum(bean interface{}, colName string) (float64, error) {
return session.Sum(bean, colName) return session.Sum(bean, colName)
} }
// SumInt sum the records by some column. bean's non-empty fields are conditions.
func (engine *Engine) SumInt(bean interface{}, colName string) (int64, error) {
session := engine.NewSession()
defer session.Close()
return session.SumInt(bean, colName)
}
// Sums sum the records by some columns. bean's non-empty fields are conditions. // Sums sum the records by some columns. bean's non-empty fields are conditions.
func (engine *Engine) Sums(bean interface{}, colNames ...string) ([]float64, error) { func (engine *Engine) Sums(bean interface{}, colNames ...string) ([]float64, error) {
session := engine.NewSession() session := engine.NewSession()
@@ -1474,7 +1544,6 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) {
results = append(results, result) results = append(results, result)
if err != nil { if err != nil {
return nil, err return nil, err
//lastError = err
} }
} }
} }
@@ -1482,49 +1551,32 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) {
return results, lastError return results, lastError
} }
// TZTime change one time to xorm time location // nowTime return current time
func (engine *Engine) TZTime(t time.Time) time.Time { func (engine *Engine) nowTime(col *core.Column) (interface{}, time.Time) {
if !t.IsZero() { // if time is not initialized it's not suitable for Time.In() t := time.Now()
return t.In(engine.TZLocation) var tz = engine.DatabaseTZ
if !col.DisableTimeZone && col.TimeZone != nil {
tz = col.TimeZone
} }
return t return engine.formatTime(col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation)
}
// NowTime return current time
func (engine *Engine) NowTime(sqlTypeName string) interface{} {
t := time.Now()
return engine.FormatTime(sqlTypeName, t)
}
// NowTime2 return current time
func (engine *Engine) NowTime2(sqlTypeName string) (interface{}, time.Time) {
t := time.Now()
return engine.FormatTime(sqlTypeName, t), t
}
// FormatTime format time
func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) {
return engine.formatTime(engine.TZLocation, sqlTypeName, t)
} }
func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{}) { func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{}) {
if col.DisableTimeZone { if t.IsZero() {
return engine.formatTime(nil, col.SQLType.Name, t) if col.Nullable {
} else if col.TimeZone != nil { return nil
return engine.formatTime(col.TimeZone, col.SQLType.Name, t) }
return ""
} }
return engine.formatTime(engine.TZLocation, col.SQLType.Name, t)
if col.TimeZone != nil {
return engine.formatTime(col.SQLType.Name, t.In(col.TimeZone))
}
return engine.formatTime(col.SQLType.Name, t.In(engine.DatabaseTZ))
} }
func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.Time) (v interface{}) { // formatTime format time as column type
if engine.dialect.DBType() == core.ORACLE { func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{}) {
return t
}
if tz != nil {
t = t.In(tz)
} else {
t = engine.TZTime(t)
}
switch sqlTypeName { switch sqlTypeName {
case core.Time: case core.Time:
s := t.Format("2006-01-02 15:04:05") //time.RFC3339 s := t.Format("2006-01-02 15:04:05") //time.RFC3339
@@ -1532,18 +1584,10 @@ func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.T
case core.Date: case core.Date:
v = t.Format("2006-01-02") v = t.Format("2006-01-02")
case core.DateTime, core.TimeStamp: case core.DateTime, core.TimeStamp:
if engine.dialect.DBType() == "ql" { v = t.Format("2006-01-02 15:04:05")
v = t
} else if engine.dialect.DBType() == "sqlite3" {
v = t.UTC().Format("2006-01-02 15:04:05")
} else {
v = t.Format("2006-01-02 15:04:05")
}
case core.TimeStampz: case core.TimeStampz:
if engine.dialect.DBType() == core.MSSQL { if engine.dialect.DBType() == core.MSSQL {
v = t.Format("2006-01-02T15:04:05.9999999Z07:00") v = t.Format("2006-01-02T15:04:05.9999999Z07:00")
} else if engine.DriverName() == "mssql" {
v = t
} else { } else {
v = t.Format(time.RFC3339Nano) v = t.Format(time.RFC3339Nano)
} }
@@ -1555,9 +1599,39 @@ func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.T
return return
} }
// GetColumnMapper returns the column name mapper
func (engine *Engine) GetColumnMapper() core.IMapper {
return engine.ColumnMapper
}
// GetTableMapper returns the table name mapper
func (engine *Engine) GetTableMapper() core.IMapper {
return engine.TableMapper
}
// GetTZLocation returns time zone of the application
func (engine *Engine) GetTZLocation() *time.Location {
return engine.TZLocation
}
// SetTZLocation sets time zone of the application
func (engine *Engine) SetTZLocation(tz *time.Location) {
engine.TZLocation = tz
}
// GetTZDatabase returns time zone of the database
func (engine *Engine) GetTZDatabase() *time.Location {
return engine.DatabaseTZ
}
// SetTZDatabase sets time zone of the database
func (engine *Engine) SetTZDatabase(tz *time.Location) {
engine.DatabaseTZ = tz
}
// Unscoped always disable struct tag "deleted" // Unscoped always disable struct tag "deleted"
func (engine *Engine) Unscoped() *Session { func (engine *Engine) Unscoped() *Session {
session := engine.NewSession() session := engine.NewSession()
session.IsAutoClose = true session.isAutoClose = true
return session.Unscoped() return session.Unscoped()
} }
+230
View File
@@ -0,0 +1,230 @@
// Copyright 2017 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 (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/go-xorm/builder"
"github.com/go-xorm/core"
)
func (engine *Engine) buildConds(table *core.Table, bean interface{},
includeVersion bool, includeUpdated bool, includeNil bool,
includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool,
mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) {
var conds []builder.Cond
for _, col := range table.Columns() {
if !includeVersion && col.IsVersion {
continue
}
if !includeUpdated && col.IsUpdated {
continue
}
if !includeAutoIncr && col.IsAutoIncrement {
continue
}
if engine.dialect.DBType() == core.MSSQL && (col.SQLType.Name == core.Text || col.SQLType.IsBlob() || col.SQLType.Name == core.TimeStampz) {
continue
}
if col.SQLType.IsJson() {
continue
}
var colName string
if addedTableName {
var nm = tableName
if len(aliasName) > 0 {
nm = aliasName
}
colName = engine.Quote(nm) + "." + engine.Quote(col.Name)
} else {
colName = engine.Quote(col.Name)
}
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
engine.logger.Error(err)
continue
}
if col.IsDeleted && !unscoped { // tag "deleted" is enabled
conds = append(conds, engine.CondDeleted(colName))
}
fieldValue := *fieldValuePtr
if fieldValue.Interface() == nil {
continue
}
fieldType := reflect.TypeOf(fieldValue.Interface())
requiredField := useAllCols
if b, ok := getFlagForColumn(mustColumnMap, col); ok {
if b {
requiredField = true
} else {
continue
}
}
if fieldType.Kind() == reflect.Ptr {
if fieldValue.IsNil() {
if includeNil {
conds = append(conds, builder.Eq{colName: nil})
}
continue
} else if !fieldValue.IsValid() {
continue
} else {
// dereference ptr type to instance type
fieldValue = fieldValue.Elem()
fieldType = reflect.TypeOf(fieldValue.Interface())
requiredField = true
}
}
var val interface{}
switch fieldType.Kind() {
case reflect.Bool:
if allUseBool || requiredField {
val = fieldValue.Interface()
} else {
// if a bool in a struct, it will not be as a condition because it default is false,
// please use Where() instead
continue
}
case reflect.String:
if !requiredField && fieldValue.String() == "" {
continue
}
// for MyString, should convert to string or panic
if fieldType.String() != reflect.String.String() {
val = fieldValue.String()
} else {
val = fieldValue.Interface()
}
case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64:
if !requiredField && fieldValue.Int() == 0 {
continue
}
val = fieldValue.Interface()
case reflect.Float32, reflect.Float64:
if !requiredField && fieldValue.Float() == 0.0 {
continue
}
val = fieldValue.Interface()
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
if !requiredField && fieldValue.Uint() == 0 {
continue
}
t := int64(fieldValue.Uint())
val = reflect.ValueOf(&t).Interface()
case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) {
t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
continue
}
val = engine.formatColTime(col, t)
} else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok {
continue
} else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok {
val, _ = valNul.Value()
if val == nil {
continue
}
} else {
if col.SQLType.IsJson() {
if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
engine.logger.Error(err)
continue
}
val = string(bytes)
} else if col.SQLType.IsBlob() {
var bytes []byte
var err error
bytes, err = json.Marshal(fieldValue.Interface())
if err != nil {
engine.logger.Error(err)
continue
}
val = bytes
}
} else {
engine.autoMapType(fieldValue)
if table, ok := engine.Tables[fieldValue.Type()]; ok {
if len(table.PrimaryKeys) == 1 {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
// fix non-int pk issues
//if pkField.Int() != 0 {
if pkField.IsValid() && !isZero(pkField.Interface()) {
val = pkField.Interface()
} else {
continue
}
} else {
//TODO: how to handler?
return nil, fmt.Errorf("not supported %v as %v", fieldValue.Interface(), table.PrimaryKeys)
}
} else {
val = fieldValue.Interface()
}
}
}
case reflect.Array:
continue
case reflect.Slice, reflect.Map:
if fieldValue == reflect.Zero(fieldType) {
continue
}
if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 {
continue
}
if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
engine.logger.Error(err)
continue
}
val = string(bytes)
} else if col.SQLType.IsBlob() {
var bytes []byte
var err error
if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) &&
fieldType.Elem().Kind() == reflect.Uint8 {
if fieldValue.Len() > 0 {
val = fieldValue.Bytes()
} else {
continue
}
} else {
bytes, err = json.Marshal(fieldValue.Interface())
if err != nil {
engine.logger.Error(err)
continue
}
val = bytes
}
} else {
continue
}
default:
val = fieldValue.Interface()
}
conds = append(conds, builder.Eq{colName: val})
}
return builder.And(conds...), nil
}
+194
View File
@@ -0,0 +1,194 @@
// Copyright 2017 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 (
"github.com/go-xorm/core"
)
// EngineGroup defines an engine group
type EngineGroup struct {
*Engine
slaves []*Engine
policy GroupPolicy
}
// NewEngineGroup creates a new engine group
func NewEngineGroup(args1 interface{}, args2 interface{}, policies ...GroupPolicy) (*EngineGroup, error) {
var eg EngineGroup
if len(policies) > 0 {
eg.policy = policies[0]
} else {
eg.policy = RoundRobinPolicy()
}
driverName, ok1 := args1.(string)
conns, ok2 := args2.([]string)
if ok1 && ok2 {
engines := make([]*Engine, len(conns))
for i, conn := range conns {
engine, err := NewEngine(driverName, conn)
if err != nil {
return nil, err
}
engine.engineGroup = &eg
engines[i] = engine
}
eg.Engine = engines[0]
eg.slaves = engines[1:]
return &eg, nil
}
master, ok3 := args1.(*Engine)
slaves, ok4 := args2.([]*Engine)
if ok3 && ok4 {
master.engineGroup = &eg
for i := 0; i < len(slaves); i++ {
slaves[i].engineGroup = &eg
}
eg.Engine = master
eg.slaves = slaves
return &eg, nil
}
return nil, ErrParamsType
}
// Close the engine
func (eg *EngineGroup) Close() error {
err := eg.Engine.Close()
if err != nil {
return err
}
for i := 0; i < len(eg.slaves); i++ {
err := eg.slaves[i].Close()
if err != nil {
return err
}
}
return nil
}
// Master returns the master engine
func (eg *EngineGroup) Master() *Engine {
return eg.Engine
}
// Ping tests if database is alive
func (eg *EngineGroup) Ping() error {
if err := eg.Engine.Ping(); err != nil {
return err
}
for _, slave := range eg.slaves {
if err := slave.Ping(); err != nil {
return err
}
}
return nil
}
// SetColumnMapper set the column name mapping rule
func (eg *EngineGroup) SetColumnMapper(mapper core.IMapper) {
eg.Engine.ColumnMapper = mapper
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].ColumnMapper = mapper
}
}
// SetDefaultCacher set the default cacher
func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) {
eg.Engine.SetDefaultCacher(cacher)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].SetDefaultCacher(cacher)
}
}
// SetLogger set the new logger
func (eg *EngineGroup) SetLogger(logger core.ILogger) {
eg.Engine.SetLogger(logger)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].SetLogger(logger)
}
}
// SetLogLevel sets the logger level
func (eg *EngineGroup) SetLogLevel(level core.LogLevel) {
eg.Engine.SetLogLevel(level)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].SetLogLevel(level)
}
}
// SetMapper set the name mapping rules
func (eg *EngineGroup) SetMapper(mapper core.IMapper) {
eg.Engine.SetMapper(mapper)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].SetMapper(mapper)
}
}
// SetMaxIdleConns set the max idle connections on pool, default is 2
func (eg *EngineGroup) SetMaxIdleConns(conns int) {
eg.Engine.db.SetMaxIdleConns(conns)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].db.SetMaxIdleConns(conns)
}
}
// SetMaxOpenConns is only available for go 1.2+
func (eg *EngineGroup) SetMaxOpenConns(conns int) {
eg.Engine.db.SetMaxOpenConns(conns)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].db.SetMaxOpenConns(conns)
}
}
// SetPolicy set the group policy
func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup {
eg.policy = policy
return eg
}
// SetTableMapper set the table name mapping rule
func (eg *EngineGroup) SetTableMapper(mapper core.IMapper) {
eg.Engine.TableMapper = mapper
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].TableMapper = mapper
}
}
// ShowExecTime show SQL statement and execute time or not on logger if log level is great than INFO
func (eg *EngineGroup) ShowExecTime(show ...bool) {
eg.Engine.ShowExecTime(show...)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].ShowExecTime(show...)
}
}
// ShowSQL show SQL statement or not on logger if log level is great than INFO
func (eg *EngineGroup) ShowSQL(show ...bool) {
eg.Engine.ShowSQL(show...)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].ShowSQL(show...)
}
}
// Slave returns one of the physical databases which is a slave according the policy
func (eg *EngineGroup) Slave() *Engine {
switch len(eg.slaves) {
case 0:
return eg.Engine
case 1:
return eg.slaves[0]
}
return eg.policy.Slave(eg)
}
// Slaves returns all the slaves
func (eg *EngineGroup) Slaves() []*Engine {
return eg.slaves
}
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2017 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 (
"math/rand"
"sync"
"time"
)
// GroupPolicy is be used by chosing the current slave from slaves
type GroupPolicy interface {
Slave(*EngineGroup) *Engine
}
// GroupPolicyHandler should be used when a function is a GroupPolicy
type GroupPolicyHandler func(*EngineGroup) *Engine
// Slave implements the chosen of slaves
func (h GroupPolicyHandler) Slave(eg *EngineGroup) *Engine {
return h(eg)
}
// RandomPolicy implmentes randomly chose the slave of slaves
func RandomPolicy() GroupPolicyHandler {
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
return func(g *EngineGroup) *Engine {
return g.Slaves()[r.Intn(len(g.Slaves()))]
}
}
// WeightRandomPolicy implmentes randomly chose the slave of slaves
func WeightRandomPolicy(weights []int) GroupPolicyHandler {
var rands = make([]int, 0, len(weights))
for i := 0; i < len(weights); i++ {
for n := 0; n < weights[i]; n++ {
rands = append(rands, i)
}
}
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
return func(g *EngineGroup) *Engine {
var slaves = g.Slaves()
idx := rands[r.Intn(len(rands))]
if idx >= len(slaves) {
idx = len(slaves) - 1
}
return slaves[idx]
}
}
func RoundRobinPolicy() GroupPolicyHandler {
var pos = -1
var lock sync.Mutex
return func(g *EngineGroup) *Engine {
var slaves = g.Slaves()
lock.Lock()
defer lock.Unlock()
pos++
if pos >= len(slaves) {
pos = 0
}
return slaves[pos]
}
}
func WeightRoundRobinPolicy(weights []int) GroupPolicyHandler {
var rands = make([]int, 0, len(weights))
for i := 0; i < len(weights); i++ {
for n := 0; n < weights[i]; n++ {
rands = append(rands, i)
}
}
var pos = -1
var lock sync.Mutex
return func(g *EngineGroup) *Engine {
var slaves = g.Slaves()
lock.Lock()
defer lock.Unlock()
pos++
if pos >= len(rands) {
pos = 0
}
idx := rands[pos]
if idx >= len(slaves) {
idx = len(slaves) - 1
}
return slaves[idx]
}
}
// LeastConnPolicy implements GroupPolicy, every time will get the least connections slave
func LeastConnPolicy() GroupPolicyHandler {
return func(g *EngineGroup) *Engine {
var slaves = g.Slaves()
connections := 0
idx := 0
for i := 0; i < len(slaves); i++ {
openConnections := slaves[i].DB().Stats().OpenConnections
if i == 0 {
connections = openConnections
idx = i
} else if openConnections <= connections {
connections = openConnections
idx = i
}
}
return slaves[idx]
}
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2017 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.
// +build go1.6
package xorm
import "time"
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
func (engine *Engine) SetConnMaxLifetime(d time.Duration) {
engine.db.SetConnMaxLifetime(d)
}
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
func (eg *EngineGroup) SetConnMaxLifetime(d time.Duration) {
eg.Engine.SetConnMaxLifetime(d)
for i := 0; i < len(eg.slaves); i++ {
eg.slaves[i].SetConnMaxLifetime(d)
}
}
+2
View File
@@ -23,4 +23,6 @@ var (
ErrNeedDeletedCond = errors.New("Delete need at least one condition") ErrNeedDeletedCond = errors.New("Delete need at least one condition")
// ErrNotImplemented not implemented // ErrNotImplemented not implemented
ErrNotImplemented = errors.New("Not implemented") ErrNotImplemented = errors.New("Not implemented")
// ErrConditionType condition type unsupported
ErrConditionType = errors.New("Unsupported conditon type")
) )
+54 -203
View File
@@ -196,25 +196,43 @@ func isArrayValueZero(v reflect.Value) bool {
func int64ToIntValue(id int64, tp reflect.Type) reflect.Value { func int64ToIntValue(id int64, tp reflect.Type) reflect.Value {
var v interface{} var v interface{}
switch tp.Kind() { kind := tp.Kind()
case reflect.Int16:
v = int16(id) if kind == reflect.Ptr {
case reflect.Int32: kind = tp.Elem().Kind()
v = int32(id)
case reflect.Int:
v = int(id)
case reflect.Int64:
v = id
case reflect.Uint16:
v = uint16(id)
case reflect.Uint32:
v = uint32(id)
case reflect.Uint64:
v = uint64(id)
case reflect.Uint:
v = uint(id)
} }
return reflect.ValueOf(v).Convert(tp)
switch kind {
case reflect.Int16:
temp := int16(id)
v = &temp
case reflect.Int32:
temp := int32(id)
v = &temp
case reflect.Int:
temp := int(id)
v = &temp
case reflect.Int64:
temp := id
v = &temp
case reflect.Uint16:
temp := uint16(id)
v = &temp
case reflect.Uint32:
temp := uint32(id)
v = &temp
case reflect.Uint64:
temp := uint64(id)
v = &temp
case reflect.Uint:
temp := uint(id)
v = &temp
}
if tp.Kind() == reflect.Ptr {
return reflect.ValueOf(v).Convert(tp)
}
return reflect.ValueOf(v).Elem().Convert(tp)
} }
func int64ToInt(id int64, tp reflect.Type) interface{} { func int64ToInt(id int64, tp reflect.Type) interface{} {
@@ -302,180 +320,6 @@ func sliceEq(left, right []string) bool {
return true return true
} }
func reflect2value(rawValue *reflect.Value) (str string, err error) {
aa := reflect.TypeOf((*rawValue).Interface())
vv := reflect.ValueOf((*rawValue).Interface())
switch aa.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
str = strconv.FormatInt(vv.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
str = strconv.FormatUint(vv.Uint(), 10)
case reflect.Float32, reflect.Float64:
str = strconv.FormatFloat(vv.Float(), 'f', -1, 64)
case reflect.String:
str = vv.String()
case reflect.Array, reflect.Slice:
switch aa.Elem().Kind() {
case reflect.Uint8:
data := rawValue.Interface().([]byte)
str = string(data)
default:
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
}
// time type
case reflect.Struct:
if aa.ConvertibleTo(core.TimeType) {
str = vv.Convert(core.TimeType).Interface().(time.Time).Format(time.RFC3339Nano)
} else {
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
}
case reflect.Bool:
str = strconv.FormatBool(vv.Bool())
case reflect.Complex128, reflect.Complex64:
str = fmt.Sprintf("%v", vv.Complex())
/* TODO: unsupported types below
case reflect.Map:
case reflect.Ptr:
case reflect.Uintptr:
case reflect.UnsafePointer:
case reflect.Chan, reflect.Func, reflect.Interface:
*/
default:
err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
}
return
}
func value2Bytes(rawValue *reflect.Value) (data []byte, err error) {
var str string
str, err = reflect2value(rawValue)
if err != nil {
return
}
data = []byte(str)
return
}
func value2String(rawValue *reflect.Value) (data string, err error) {
data, err = reflect2value(rawValue)
if err != nil {
return
}
return
}
func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
for rows.Next() {
result, err := row2mapStr(rows, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, result)
}
return resultsSlice, nil
}
func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
for rows.Next() {
result, err := row2map(rows, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, result)
}
return resultsSlice, nil
}
func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) {
result := make(map[string][]byte)
scanResultContainers := make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ {
var scanResultContainer interface{}
scanResultContainers[i] = &scanResultContainer
}
if err := rows.Scan(scanResultContainers...); err != nil {
return nil, err
}
for ii, key := range fields {
rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii]))
//if row is null then ignore
if rawValue.Interface() == nil {
//fmt.Println("ignore ...", key, rawValue)
continue
}
if data, err := value2Bytes(&rawValue); err == nil {
result[key] = data
} else {
return nil, err // !nashtsai! REVIEW, should return err or just error log?
}
}
return result, nil
}
func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) {
result := make(map[string]string)
scanResultContainers := make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ {
var scanResultContainer interface{}
scanResultContainers[i] = &scanResultContainer
}
if err := rows.Scan(scanResultContainers...); err != nil {
return nil, err
}
for ii, key := range fields {
rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii]))
//if row is null then ignore
if rawValue.Interface() == nil {
//fmt.Println("ignore ...", key, rawValue)
continue
}
if data, err := value2String(&rawValue); err == nil {
result[key] = data
} else {
return nil, err // !nashtsai! REVIEW, should return err or just error log?
}
}
return result, nil
}
func txQuery2(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) {
rows, err := tx.Query(sqlStr, params...)
if err != nil {
return nil, err
}
defer rows.Close()
return rows2Strings(rows)
}
func query2(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) {
s, err := db.Prepare(sqlStr)
if err != nil {
return nil, err
}
defer s.Close()
rows, err := s.Query(params...)
if err != nil {
return nil, err
}
defer rows.Close()
return rows2Strings(rows)
}
func setColumnInt(bean interface{}, col *core.Column, t int64) { func setColumnInt(bean interface{}, col *core.Column, t int64) {
v, err := col.ValueOf(bean) v, err := col.ValueOf(bean)
if err != nil { if err != nil {
@@ -514,7 +358,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
for _, col := range table.Columns() { for _, col := range table.Columns() {
if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated { if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated {
if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue continue
} }
} }
@@ -542,6 +386,10 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
if len(fieldValue.String()) == 0 { if len(fieldValue.String()) == 0 {
continue continue
} }
case reflect.Ptr:
if fieldValue.Pointer() == 0 {
continue
}
} }
} }
@@ -549,28 +397,32 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
continue continue
} }
if session.Statement.ColumnStr != "" { if session.statement.ColumnStr != "" {
if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { 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 continue
} }
} }
if session.Statement.OmitStr != "" { if session.statement.OmitStr != "" {
if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok { if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue continue
} }
} }
// !evalphobia! set fieldValue as nil when column is nullable and zero-value // !evalphobia! set fieldValue as nil when column is nullable and zero-value
if _, ok := getFlagForColumn(session.Statement.nullableMap, col); ok { if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) { if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int var nilValue *int
fieldValue = reflect.ValueOf(nilValue) fieldValue = reflect.ValueOf(nilValue)
} }
} }
if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ { if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time // if time is non-empty, then set to auto time
val, t := session.Engine.NowTime2(col.SQLType.Name) val, t := session.engine.nowTime(col)
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name
@@ -578,7 +430,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
col := table.GetColumn(colName) col := table.GetColumn(colName)
setColumnTime(bean, col, t) setColumnTime(bean, col, t)
}) })
} else if col.IsVersion && session.Statement.checkVersion { } else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1) args = append(args, 1)
} else { } else {
arg, err := session.value2Interface(col, fieldValue) arg, err := session.value2Interface(col, fieldValue)
@@ -589,7 +441,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
} }
if includeQuote { if includeQuote {
colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?") colNames = append(colNames, session.engine.Quote(col.Name)+" = ?")
} else { } else {
colNames = append(colNames, col.Name) colNames = append(colNames, col.Name)
} }
@@ -602,7 +454,6 @@ func indexName(tableName, idxName string) string {
} }
func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) { func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) {
if len(m) == 0 { if len(m) == 0 {
return false, false return false, false
} }
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017 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 "time"
const (
zeroTime0 = "0000-00-00 00:00:00"
zeroTime1 = "0001-01-01 00:00:00"
)
func formatTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
func isTimeZero(t time.Time) bool {
return t.IsZero() || formatTime(t) == zeroTime0 ||
formatTime(t) == zeroTime1
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright 2017 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 (
"database/sql"
"reflect"
"time"
"github.com/go-xorm/core"
)
// Interface defines the interface which Engine, EngineGroup and Session will implementate.
type Interface interface {
AllCols() *Session
Alias(alias string) *Session
Asc(colNames ...string) *Session
BufferSize(size int) *Session
Cols(columns ...string) *Session
Count(...interface{}) (int64, error)
CreateIndexes(bean interface{}) error
CreateUniques(bean interface{}) error
Decr(column string, arg ...interface{}) *Session
Desc(...string) *Session
Delete(interface{}) (int64, error)
Distinct(columns ...string) *Session
DropIndexes(bean interface{}) error
Exec(string, ...interface{}) (sql.Result, error)
Exist(bean ...interface{}) (bool, error)
Find(interface{}, ...interface{}) error
Get(interface{}) (bool, error)
GroupBy(keys string) *Session
ID(interface{}) *Session
In(string, ...interface{}) *Session
Incr(column string, arg ...interface{}) *Session
Insert(...interface{}) (int64, error)
InsertOne(interface{}) (int64, error)
IsTableEmpty(bean interface{}) (bool, error)
IsTableExist(beanOrTableName interface{}) (bool, error)
Iterate(interface{}, IterFunc) error
Limit(int, ...int) *Session
NoAutoCondition(...bool) *Session
NotIn(string, ...interface{}) *Session
Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session
Omit(columns ...string) *Session
OrderBy(order string) *Session
Ping() error
Query(sqlOrAgrs ...interface{}) (resultsSlice []map[string][]byte, err error)
QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error)
QueryString(sqlorArgs ...interface{}) ([]map[string]string, error)
Rows(bean interface{}) (*Rows, error)
SetExpr(string, string) *Session
SQL(interface{}, ...interface{}) *Session
Sum(bean interface{}, colName string) (float64, error)
SumInt(bean interface{}, colName string) (int64, error)
Sums(bean interface{}, colNames ...string) ([]float64, error)
SumsInt(bean interface{}, colNames ...string) ([]int64, error)
Table(tableNameOrBean interface{}) *Session
Unscoped() *Session
Update(bean interface{}, condiBeans ...interface{}) (int64, error)
UseBool(...string) *Session
Where(interface{}, ...interface{}) *Session
}
// EngineInterface defines the interface which Engine, EngineGroup will implementate.
type EngineInterface interface {
Interface
Before(func(interface{})) *Session
Charset(charset string) *Session
CreateTables(...interface{}) error
DBMetas() ([]*core.Table, error)
Dialect() core.Dialect
DropTables(...interface{}) error
DumpAllToFile(fp string, tp ...core.DbType) error
GetColumnMapper() core.IMapper
GetDefaultCacher() core.Cacher
GetTableMapper() core.IMapper
GetTZDatabase() *time.Location
GetTZLocation() *time.Location
NewSession() *Session
NoAutoTime() *Session
Quote(string) string
SetDefaultCacher(core.Cacher)
SetLogLevel(core.LogLevel)
SetMapper(core.IMapper)
SetTZDatabase(tz *time.Location)
SetTZLocation(tz *time.Location)
ShowSQL(show ...bool)
Sync(...interface{}) error
Sync2(...interface{}) error
StoreEngine(storeEngine string) *Session
TableInfo(bean interface{}) *Table
UnMapType(reflect.Type)
}
var (
_ Interface = &Session{}
_ EngineInterface = &Engine{}
_ EngineInterface = &EngineGroup{}
)
+33 -7
View File
@@ -29,13 +29,6 @@ type AfterSetProcessor interface {
AfterSet(string, Cell) AfterSet(string, Cell)
} }
// !nashtsai! TODO enable BeforeValidateProcessor when xorm start to support validations
//// Executed before an object is validated
//type BeforeValidateProcessor interface {
// BeforeValidate()
//}
// --
// AfterInsertProcessor executed after an object is persisted to the database // AfterInsertProcessor executed after an object is persisted to the database
type AfterInsertProcessor interface { type AfterInsertProcessor interface {
AfterInsert() AfterInsert()
@@ -50,3 +43,36 @@ type AfterUpdateProcessor interface {
type AfterDeleteProcessor interface { type AfterDeleteProcessor interface {
AfterDelete() AfterDelete()
} }
// AfterLoadProcessor executed after an ojbect has been loaded from database
type AfterLoadProcessor interface {
AfterLoad()
}
// AfterLoadSessionProcessor executed after an ojbect has been loaded from database with session parameter
type AfterLoadSessionProcessor interface {
AfterLoad(*Session)
}
type executedProcessorFunc func(*Session, interface{}) error
type executedProcessor struct {
fun executedProcessorFunc
session *Session
bean interface{}
}
func (executor *executedProcessor) execute() error {
return executor.fun(executor.session, executor.bean)
}
func (session *Session) executeProcessors() error {
processors := session.afterProcessors
session.afterProcessors = make([]executedProcessor, 0)
for _, processor := range processors {
if err := processor.execute(); err != nil {
return err
}
}
return nil
}
+32 -46
View File
@@ -17,7 +17,6 @@ type Rows struct {
NoTypeCheck bool NoTypeCheck bool
session *Session session *Session
stmt *core.Stmt
rows *core.Rows rows *core.Rows
fields []string fields []string
beanType reflect.Type beanType reflect.Type
@@ -29,50 +28,33 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
rows.session = session rows.session = session
rows.beanType = reflect.Indirect(reflect.ValueOf(bean)).Type() rows.beanType = reflect.Indirect(reflect.ValueOf(bean)).Type()
defer rows.session.resetStatement()
var sqlStr string var sqlStr string
var args []interface{} var args []interface{}
var err error
rows.session.Statement.setRefValue(rValue(bean)) if err = rows.session.statement.setRefValue(rValue(bean)); err != nil {
if len(session.Statement.TableName()) <= 0 { return nil, err
}
if len(session.statement.TableName()) <= 0 {
return nil, ErrTableNotFound return nil, ErrTableNotFound
} }
if rows.session.Statement.RawSQL == "" { if rows.session.statement.RawSQL == "" {
sqlStr, args = rows.session.Statement.genGetSQL(bean) sqlStr, args, err = rows.session.statement.genGetSQL(bean)
} else {
sqlStr = rows.session.Statement.RawSQL
args = rows.session.Statement.RawParams
}
for _, filter := range rows.session.Engine.dialect.Filters() {
sqlStr = filter.Do(sqlStr, session.Engine.dialect, rows.session.Statement.RefTable)
}
rows.session.saveLastSQL(sqlStr, args...)
var err error
if rows.session.prepareStmt {
rows.stmt, err = rows.session.DB().Prepare(sqlStr)
if err != nil { if err != nil {
rows.lastError = err
rows.Close()
return nil, err
}
rows.rows, err = rows.stmt.Query(args...)
if err != nil {
rows.lastError = err
rows.Close()
return nil, err return nil, err
} }
} else { } else {
rows.rows, err = rows.session.DB().Query(sqlStr, args...) sqlStr = rows.session.statement.RawSQL
if err != nil { args = rows.session.statement.RawParams
rows.lastError = err }
rows.Close()
return nil, err rows.rows, err = rows.session.queryRows(sqlStr, args...)
} if err != nil {
rows.lastError = err
rows.Close()
return nil, err
} }
rows.fields, err = rows.rows.Columns() rows.fields, err = rows.rows.Columns()
@@ -113,15 +95,26 @@ func (rows *Rows) Scan(bean interface{}) error {
} }
dataStruct := rValue(bean) dataStruct := rValue(bean)
rows.session.Statement.setRefValue(dataStruct) if err := rows.session.statement.setRefValue(dataStruct); err != nil {
_, err := rows.session.row2Bean(rows.rows, rows.fields, len(rows.fields), bean, &dataStruct, rows.session.Statement.RefTable) return err
}
return err scanResults, err := rows.session.row2Slice(rows.rows, rows.fields, bean)
if err != nil {
return err
}
_, err = rows.session.slice2Bean(scanResults, rows.fields, bean, &dataStruct, rows.session.statement.RefTable)
if err != nil {
return err
}
return rows.session.executeProcessors()
} }
// Close session if session.IsAutoClose is true, and claimed any opened resources // Close session if session.IsAutoClose is true, and claimed any opened resources
func (rows *Rows) Close() error { func (rows *Rows) Close() error {
if rows.session.IsAutoClose { if rows.session.isAutoClose {
defer rows.session.Close() defer rows.session.Close()
} }
@@ -129,17 +122,10 @@ func (rows *Rows) Close() error {
if rows.rows != nil { if rows.rows != nil {
rows.lastError = rows.rows.Close() rows.lastError = rows.rows.Close()
if rows.lastError != nil { if rows.lastError != nil {
defer rows.stmt.Close()
return rows.lastError return rows.lastError
} }
} }
if rows.stmt != nil {
rows.lastError = rows.stmt.Close()
}
} else { } else {
if rows.stmt != nil {
defer rows.stmt.Close()
}
if rows.rows != nil { if rows.rows != nil {
defer rows.rows.Close() defer rows.rows.Close()
} }
+188 -185
View File
@@ -11,7 +11,6 @@ import (
"fmt" "fmt"
"hash/crc32" "hash/crc32"
"reflect" "reflect"
"strconv"
"strings" "strings"
"time" "time"
@@ -22,17 +21,16 @@ import (
// kind of database operations. // kind of database operations.
type Session struct { type Session struct {
db *core.DB db *core.DB
Engine *Engine engine *Engine
Tx *core.Tx tx *core.Tx
Statement Statement statement Statement
IsAutoCommit bool isAutoCommit bool
IsCommitedOrRollbacked bool isCommitedOrRollbacked bool
TransType string isAutoClose bool
IsAutoClose bool
// Automatically reset the statement after operations that execute a SQL // Automatically reset the statement after operations that execute a SQL
// query such as Count(), Find(), Get(), ... // query such as Count(), Find(), Get(), ...
AutoResetStatement bool autoResetStatement bool
// !nashtsai! storing these beans due to yet committed tx // !nashtsai! storing these beans due to yet committed tx
afterInsertBeans map[interface{}]*[]func(interface{}) afterInsertBeans map[interface{}]*[]func(interface{})
@@ -43,14 +41,17 @@ type Session struct {
beforeClosures []func(interface{}) beforeClosures []func(interface{})
afterClosures []func(interface{}) afterClosures []func(interface{})
afterProcessors []executedProcessor
prepareStmt bool prepareStmt bool
stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr)) stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr))
cascadeDeep int
// !evalphobia! stored the last executed query on this session // !evalphobia! stored the last executed query on this session
//beforeSQLExec func(string, ...interface{}) //beforeSQLExec func(string, ...interface{})
lastSQL string lastSQL string
lastSQLArgs []interface{} lastSQLArgs []interface{}
err error
} }
// Clone copy all the session's content and return a new session // Clone copy all the session's content and return a new session
@@ -61,12 +62,12 @@ func (session *Session) Clone() *Session {
// Init reset the session as the init status. // Init reset the session as the init status.
func (session *Session) Init() { func (session *Session) Init() {
session.Statement.Init() session.statement.Init()
session.Statement.Engine = session.Engine session.statement.Engine = session.engine
session.IsAutoCommit = true session.isAutoCommit = true
session.IsCommitedOrRollbacked = false session.isCommitedOrRollbacked = false
session.IsAutoClose = false session.isAutoClose = false
session.AutoResetStatement = true session.autoResetStatement = true
session.prepareStmt = false session.prepareStmt = false
// !nashtsai! is lazy init better? // !nashtsai! is lazy init better?
@@ -75,6 +76,9 @@ func (session *Session) Init() {
session.afterDeleteBeans = make(map[interface{}]*[]func(interface{}), 0) session.afterDeleteBeans = make(map[interface{}]*[]func(interface{}), 0)
session.beforeClosures = make([]func(interface{}), 0) session.beforeClosures = make([]func(interface{}), 0)
session.afterClosures = make([]func(interface{}), 0) session.afterClosures = make([]func(interface{}), 0)
session.stmtCache = make(map[uint32]*core.Stmt)
session.afterProcessors = make([]executedProcessor, 0)
session.lastSQL = "" session.lastSQL = ""
session.lastSQLArgs = []interface{}{} session.lastSQLArgs = []interface{}{}
@@ -89,19 +93,23 @@ func (session *Session) Close() {
if session.db != nil { if session.db != nil {
// When Close be called, if session is a transaction and do not call // When Close be called, if session is a transaction and do not call
// Commit or Rollback, then call Rollback. // Commit or Rollback, then call Rollback.
if session.Tx != nil && !session.IsCommitedOrRollbacked { if session.tx != nil && !session.isCommitedOrRollbacked {
session.Rollback() session.Rollback()
} }
session.Tx = nil session.tx = nil
session.stmtCache = nil session.stmtCache = nil
session.Init()
session.db = nil session.db = nil
} }
} }
// IsClosed returns if session is closed
func (session *Session) IsClosed() bool {
return session.db == nil
}
func (session *Session) resetStatement() { func (session *Session) resetStatement() {
if session.AutoResetStatement { if session.autoResetStatement {
session.Statement.Init() session.statement.Init()
} }
} }
@@ -129,75 +137,75 @@ func (session *Session) After(closures func(interface{})) *Session {
// Table can input a string or pointer to struct for special a table to operate. // Table can input a string or pointer to struct for special a table to operate.
func (session *Session) Table(tableNameOrBean interface{}) *Session { func (session *Session) Table(tableNameOrBean interface{}) *Session {
session.Statement.Table(tableNameOrBean) session.statement.Table(tableNameOrBean)
return session return session
} }
// Alias set the table alias // Alias set the table alias
func (session *Session) Alias(alias string) *Session { func (session *Session) Alias(alias string) *Session {
session.Statement.Alias(alias) session.statement.Alias(alias)
return session return session
} }
// NoCascade indicate that no cascade load child object // NoCascade indicate that no cascade load child object
func (session *Session) NoCascade() *Session { func (session *Session) NoCascade() *Session {
session.Statement.UseCascade = false session.statement.UseCascade = false
return session return session
} }
// ForUpdate Set Read/Write locking for UPDATE // ForUpdate Set Read/Write locking for UPDATE
func (session *Session) ForUpdate() *Session { func (session *Session) ForUpdate() *Session {
session.Statement.IsForUpdate = true session.statement.IsForUpdate = true
return session return session
} }
// NoAutoCondition disable generate SQL condition from beans // NoAutoCondition disable generate SQL condition from beans
func (session *Session) NoAutoCondition(no ...bool) *Session { func (session *Session) NoAutoCondition(no ...bool) *Session {
session.Statement.NoAutoCondition(no...) session.statement.NoAutoCondition(no...)
return session return session
} }
// Limit provide limit and offset query condition // Limit provide limit and offset query condition
func (session *Session) Limit(limit int, start ...int) *Session { func (session *Session) Limit(limit int, start ...int) *Session {
session.Statement.Limit(limit, start...) session.statement.Limit(limit, start...)
return session return session
} }
// OrderBy provide order by query condition, the input parameter is the content // OrderBy provide order by query condition, the input parameter is the content
// after order by on a sql statement. // after order by on a sql statement.
func (session *Session) OrderBy(order string) *Session { func (session *Session) OrderBy(order string) *Session {
session.Statement.OrderBy(order) session.statement.OrderBy(order)
return session return session
} }
// Desc provide desc order by query condition, the input parameters are columns. // Desc provide desc order by query condition, the input parameters are columns.
func (session *Session) Desc(colNames ...string) *Session { func (session *Session) Desc(colNames ...string) *Session {
session.Statement.Desc(colNames...) session.statement.Desc(colNames...)
return session return session
} }
// Asc provide asc order by query condition, the input parameters are columns. // Asc provide asc order by query condition, the input parameters are columns.
func (session *Session) Asc(colNames ...string) *Session { func (session *Session) Asc(colNames ...string) *Session {
session.Statement.Asc(colNames...) session.statement.Asc(colNames...)
return session return session
} }
// StoreEngine is only avialble mysql dialect currently // StoreEngine is only avialble mysql dialect currently
func (session *Session) StoreEngine(storeEngine string) *Session { func (session *Session) StoreEngine(storeEngine string) *Session {
session.Statement.StoreEngine = storeEngine session.statement.StoreEngine = storeEngine
return session return session
} }
// Charset is only avialble mysql dialect currently // Charset is only avialble mysql dialect currently
func (session *Session) Charset(charset string) *Session { func (session *Session) Charset(charset string) *Session {
session.Statement.Charset = charset session.statement.Charset = charset
return session return session
} }
// Cascade indicates if loading sub Struct // Cascade indicates if loading sub Struct
func (session *Session) Cascade(trueOrFalse ...bool) *Session { func (session *Session) Cascade(trueOrFalse ...bool) *Session {
if len(trueOrFalse) >= 1 { if len(trueOrFalse) >= 1 {
session.Statement.UseCascade = trueOrFalse[0] session.statement.UseCascade = trueOrFalse[0]
} }
return session return session
} }
@@ -205,32 +213,32 @@ func (session *Session) Cascade(trueOrFalse ...bool) *Session {
// NoCache ask this session do not retrieve data from cache system and // NoCache ask this session do not retrieve data from cache system and
// get data from database directly. // get data from database directly.
func (session *Session) NoCache() *Session { func (session *Session) NoCache() *Session {
session.Statement.UseCache = false session.statement.UseCache = false
return session return session
} }
// Join join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN // Join join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (session *Session) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session { func (session *Session) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session {
session.Statement.Join(joinOperator, tablename, condition, args...) session.statement.Join(joinOperator, tablename, condition, args...)
return session return session
} }
// GroupBy Generate Group By statement // GroupBy Generate Group By statement
func (session *Session) GroupBy(keys string) *Session { func (session *Session) GroupBy(keys string) *Session {
session.Statement.GroupBy(keys) session.statement.GroupBy(keys)
return session return session
} }
// Having Generate Having statement // Having Generate Having statement
func (session *Session) Having(conditions string) *Session { func (session *Session) Having(conditions string) *Session {
session.Statement.Having(conditions) session.statement.Having(conditions)
return session return session
} }
// DB db return the wrapper of sql.DB // DB db return the wrapper of sql.DB
func (session *Session) DB() *core.DB { func (session *Session) DB() *core.DB {
if session.db == nil { if session.db == nil {
session.db = session.Engine.db session.db = session.engine.db
session.stmtCache = make(map[uint32]*core.Stmt, 0) session.stmtCache = make(map[uint32]*core.Stmt, 0)
} }
return session.db return session.db
@@ -243,25 +251,25 @@ func cleanupProcessorsClosures(slices *[]func(interface{})) {
} }
func (session *Session) canCache() bool { func (session *Session) canCache() bool {
if session.Statement.RefTable == nil || if session.statement.RefTable == nil ||
session.Statement.JoinStr != "" || session.statement.JoinStr != "" ||
session.Statement.RawSQL != "" || session.statement.RawSQL != "" ||
!session.Statement.UseCache || !session.statement.UseCache ||
session.Statement.IsForUpdate || session.statement.IsForUpdate ||
session.Tx != nil || session.tx != nil ||
len(session.Statement.selectStr) > 0 { len(session.statement.selectStr) > 0 {
return false return false
} }
return true return true
} }
func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, err error) {
crc := crc32.ChecksumIEEE([]byte(sqlStr)) crc := crc32.ChecksumIEEE([]byte(sqlStr))
// TODO try hash(sqlStr+len(sqlStr)) // TODO try hash(sqlStr+len(sqlStr))
var has bool var has bool
stmt, has = session.stmtCache[crc] stmt, has = session.stmtCache[crc]
if !has { if !has {
stmt, err = session.DB().Prepare(sqlStr) stmt, err = db.Prepare(sqlStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -273,18 +281,18 @@ func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) {
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 {
var col *core.Column var col *core.Column
if col = table.GetColumnIdx(key, idx); col == nil { if col = table.GetColumnIdx(key, idx); col == nil {
//session.Engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq()) //session.engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq())
return nil return nil
} }
fieldValue, err := col.ValueOfV(dataStruct) fieldValue, err := col.ValueOfV(dataStruct)
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return nil return nil
} }
if !fieldValue.IsValid() || !fieldValue.CanSet() { if !fieldValue.IsValid() || !fieldValue.CanSet() {
session.Engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key) session.engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key)
return nil return nil
} }
return fieldValue return fieldValue
@@ -293,28 +301,40 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *c
// Cell cell is a result of one column field // Cell cell is a result of one column field
type Cell *interface{} type Cell *interface{}
func (session *Session) rows2Beans(rows *core.Rows, fields []string, fieldsCount int, func (session *Session) rows2Beans(rows *core.Rows, fields []string,
table *core.Table, newElemFunc func([]string) reflect.Value, table *core.Table, newElemFunc func([]string) reflect.Value,
sliceValueSetFunc func(*reflect.Value, core.PK) error) error { sliceValueSetFunc func(*reflect.Value, core.PK) error) error {
for rows.Next() { for rows.Next() {
var newValue = newElemFunc(fields) var newValue = newElemFunc(fields)
bean := newValue.Interface() bean := newValue.Interface()
dataStruct := rValue(bean) dataStruct := newValue.Elem()
pk, err := session.row2Bean(rows, fields, fieldsCount, bean, &dataStruct, table)
if err != nil {
return err
}
err = sliceValueSetFunc(&newValue, pk) // handle beforeClosures
scanResults, err := session.row2Slice(rows, fields, bean)
if err != nil { if err != nil {
return err return err
} }
pk, err := session.slice2Bean(scanResults, fields, bean, &dataStruct, table)
if err != nil {
return err
}
session.afterProcessors = append(session.afterProcessors, executedProcessor{
fun: func(*Session, interface{}) error {
return sliceValueSetFunc(&newValue, pk)
},
session: session,
bean: bean,
})
} }
return nil return nil
} }
func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) { func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interface{}) ([]interface{}, error) {
scanResults := make([]interface{}, fieldsCount) for _, closure := range session.beforeClosures {
closure(bean)
}
scanResults := make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ { for i := 0; i < len(fields); i++ {
var cell interface{} var cell interface{}
scanResults[i] = &cell scanResults[i] = &cell
@@ -328,7 +348,10 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
b.BeforeSet(key, Cell(scanResults[ii].(*interface{}))) b.BeforeSet(key, Cell(scanResults[ii].(*interface{})))
} }
} }
return scanResults, nil
}
func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) {
defer func() { defer func() {
if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet {
for ii, key := range fields { for ii, key := range fields {
@@ -337,6 +360,40 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
} }
}() }()
// handle afterClosures
for _, closure := range session.afterClosures {
session.afterProcessors = append(session.afterProcessors, executedProcessor{
fun: func(sess *Session, bean interface{}) error {
closure(bean)
return nil
},
session: session,
bean: bean,
})
}
if a, has := bean.(AfterLoadProcessor); has {
session.afterProcessors = append(session.afterProcessors, executedProcessor{
fun: func(sess *Session, bean interface{}) error {
a.AfterLoad()
return nil
},
session: session,
bean: bean,
})
}
if a, has := bean.(AfterLoadSessionProcessor); has {
session.afterProcessors = append(session.afterProcessors, executedProcessor{
fun: func(sess *Session, bean interface{}) error {
a.AfterLoad(sess)
return nil
},
session: session,
bean: bean,
})
}
var tempMap = make(map[string]int) var tempMap = make(map[string]int)
var pk core.PK var pk core.PK
for ii, key := range fields { for ii, key := range fields {
@@ -361,9 +418,11 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if fieldValue.CanAddr() { if fieldValue.CanAddr() {
if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil { if data, err := value2Bytes(&rawValue); err == nil {
structConvert.FromDB(data) if err := structConvert.FromDB(data); err != nil {
return nil, err
}
} else { } else {
session.Engine.logger.Error(err) return nil, err
} }
continue continue
} }
@@ -376,7 +435,7 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
} }
fieldValue.Interface().(core.Conversion).FromDB(data) fieldValue.Interface().(core.Conversion).FromDB(data)
} else { } else {
session.Engine.logger.Error(err) return nil, err
} }
continue continue
} }
@@ -403,17 +462,19 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
hasAssigned = true hasAssigned = true
if len(bs) > 0 { if len(bs) > 0 {
if fieldType.Kind() == reflect.String {
fieldValue.SetString(string(bs))
continue
}
if fieldValue.CanAddr() { if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface()) err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(key, err)
return nil, err return nil, err
} }
} else { } else {
x := reflect.New(fieldType) x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface()) err := json.Unmarshal(bs, x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(key, err)
return nil, err return nil, err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
@@ -438,14 +499,12 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if fieldValue.CanAddr() { if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface()) err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err)
return nil, err return nil, err
} }
} else { } else {
x := reflect.New(fieldType) x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface()) err := json.Unmarshal(bs, x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err)
return nil, err return nil, err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
@@ -462,14 +521,19 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
x := reflect.New(fieldType) x := reflect.New(fieldType)
err := json.Unmarshal(vv.Bytes(), x.Interface()) err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err)
return nil, err return nil, err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
} else { } else {
for i := 0; i < fieldValue.Len(); i++ { if fieldValue.Len() > 0 {
if i < vv.Len() { for i := 0; i < fieldValue.Len(); i++ {
fieldValue.Index(i).Set(vv.Index(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)))
} }
} }
} }
@@ -509,57 +573,38 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
} }
case reflect.Struct: case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) { if fieldType.ConvertibleTo(core.TimeType) {
dbTZ := session.engine.DatabaseTZ
if col.TimeZone != nil {
dbTZ = col.TimeZone
}
if rawValueType == core.TimeType { if rawValueType == core.TimeType {
hasAssigned = true hasAssigned = true
t := vv.Convert(core.TimeType).Interface().(time.Time) t := vv.Convert(core.TimeType).Interface().(time.Time)
z, _ := t.Zone() z, _ := t.Zone()
dbTZ := session.Engine.DatabaseTZ
if dbTZ == nil {
if session.Engine.dialect.DBType() == core.SQLITE {
dbTZ = time.UTC
} else {
dbTZ = time.Local
}
}
// set new location if database don't save timezone or give an incorrect timezone // 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 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()) 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 = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
t.Minute(), t.Second(), t.Nanosecond(), dbTZ) t.Minute(), t.Second(), t.Nanosecond(), dbTZ)
} }
// !nashtsai! convert to engine location t = t.In(session.engine.TZLocation)
if col.TimeZone == nil {
t = t.In(session.Engine.TZLocation)
} else {
t = t.In(col.TimeZone)
}
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
// t = fieldValue.Interface().(time.Time)
// z, _ = t.Zone()
// session.Engine.LogDebug("fieldValue key[%v]: %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
} else if rawValueType == core.IntType || rawValueType == core.Int64Type || } else if rawValueType == core.IntType || rawValueType == core.Int64Type ||
rawValueType == core.Int32Type { rawValueType == core.Int32Type {
hasAssigned = true hasAssigned = true
var tz *time.Location
if col.TimeZone == nil { t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation)
tz = session.Engine.TZLocation
} else {
tz = col.TimeZone
}
t := time.Unix(vv.Int(), 0).In(tz)
//vv = reflect.ValueOf(t)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else { } else {
if d, ok := vv.Interface().([]uint8); ok { if d, ok := vv.Interface().([]uint8); ok {
hasAssigned = true hasAssigned = true
t, err := session.byte2Time(col, d) t, err := session.byte2Time(col, d)
if err != nil { if err != nil {
session.Engine.logger.Error("byte2Time error:", err.Error()) session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false hasAssigned = false
} else { } else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
@@ -568,20 +613,20 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
hasAssigned = true hasAssigned = true
t, err := session.str2Time(col, d) t, err := session.str2Time(col, d)
if err != nil { if err != nil {
session.Engine.logger.Error("byte2Time error:", err.Error()) session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false hasAssigned = false
} else { } else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} }
} else { } else {
panic(fmt.Sprintf("rawValueType is %v, value is %v", rawValueType, vv.Interface())) return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface())
} }
} }
} else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
// !<winxxp>! 增加支持sql.Scanner接口的结构,如sql.NullString // !<winxxp>! 增加支持sql.Scanner接口的结构,如sql.NullString
hasAssigned = true hasAssigned = true
if err := nulVal.Scan(vv.Interface()); err != nil { if err := nulVal.Scan(vv.Interface()); err != nil {
session.Engine.logger.Error("sql.Sanner error:", err.Error()) session.engine.logger.Error("sql.Sanner error:", err.Error())
hasAssigned = false hasAssigned = false
} }
} else if col.SQLType.IsJson() { } else if col.SQLType.IsJson() {
@@ -591,7 +636,6 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len([]byte(vv.String())) > 0 { if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), x.Interface()) err := json.Unmarshal([]byte(vv.String()), x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err)
return nil, err return nil, err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
@@ -602,76 +646,45 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len(vv.Bytes()) > 0 { if len(vv.Bytes()) > 0 {
err := json.Unmarshal(vv.Bytes(), x.Interface()) err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err)
return nil, err return nil, err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
} }
} }
} else if session.Statement.UseCascade { } else if session.statement.UseCascade {
table := session.Engine.autoMapType(*fieldValue) table, err := session.engine.autoMapType(*fieldValue)
if table != nil { if err != nil {
hasAssigned = true return nil, err
if len(table.PrimaryKeys) != 1 { }
panic("unsupported non or composited primary key cascade")
}
var pk = make(core.PK, len(table.PrimaryKeys))
switch rawValueType.Kind() { hasAssigned = true
case reflect.Int64: if len(table.PrimaryKeys) != 1 {
pk[0] = vv.Int() return nil, errors.New("unsupported non or composited primary key cascade")
case reflect.Int: }
pk[0] = int(vv.Int()) var pk = make(core.PK, len(table.PrimaryKeys))
case reflect.Int32: pk[0], err = asKind(vv, rawValueType)
pk[0] = int32(vv.Int()) if err != nil {
case reflect.Int16: return nil, err
pk[0] = int16(vv.Int()) }
case reflect.Int8:
pk[0] = int8(vv.Int())
case reflect.Uint64:
pk[0] = vv.Uint()
case reflect.Uint:
pk[0] = uint(vv.Uint())
case reflect.Uint32:
pk[0] = uint32(vv.Uint())
case reflect.Uint16:
pk[0] = uint16(vv.Uint())
case reflect.Uint8:
pk[0] = uint8(vv.Uint())
case reflect.String:
pk[0] = vv.String()
case reflect.Slice:
pk[0], _ = strconv.ParseInt(string(rawValue.Interface().([]byte)), 10, 64)
default:
panic(fmt.Sprintf("unsupported primary key type: %v, %v", rawValueType, fieldValue))
}
if !isPKZero(pk) { if !isPKZero(pk) {
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch // !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 // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
// property to be fetched lazily // property to be fetched lazily
structInter := reflect.New(fieldValue.Type()) structInter := reflect.New(fieldValue.Type())
newsession := session.Engine.NewSession() has, err := session.ID(pk).NoCascade().get(structInter.Interface())
defer newsession.Close() if err != nil {
has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface()) return nil, err
if err != nil { }
return nil, err if has {
} fieldValue.Set(structInter.Elem())
if has { } else {
//v := structInter.Elem().Interface() return nil, errors.New("cascade obj is not exist")
//fieldValue.Set(reflect.ValueOf(v))
fieldValue.Set(structInter.Elem())
} else {
return nil, errors.New("cascade obj is not exist")
}
} }
} else {
session.Engine.logger.Error("unsupported struct type in Scan: ", fieldValue.Type().String())
} }
} }
case reflect.Ptr: case reflect.Ptr:
// !nashtsai! TODO merge duplicated codes above // !nashtsai! TODO merge duplicated codes above
//typeStr := fieldType.String()
switch fieldType { switch fieldType {
// following types case matching ptr's native type, therefore assign ptr directly // following types case matching ptr's native type, therefore assign ptr directly
case core.PtrStringType: case core.PtrStringType:
@@ -769,10 +782,9 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len([]byte(vv.String())) > 0 { if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x) err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil { if err != nil {
session.Engine.logger.Error(err) return nil, err
} else {
fieldValue.Set(reflect.ValueOf(&x))
} }
fieldValue.Set(reflect.ValueOf(&x))
} }
hasAssigned = true hasAssigned = true
case core.Complex128Type: case core.Complex128Type:
@@ -780,24 +792,23 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len([]byte(vv.String())) > 0 { if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x) err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil { if err != nil {
session.Engine.logger.Error(err) return nil, err
} else {
fieldValue.Set(reflect.ValueOf(&x))
} }
fieldValue.Set(reflect.ValueOf(&x))
} }
hasAssigned = true hasAssigned = true
} // switch fieldType } // switch fieldType
// default:
// session.Engine.LogError("unsupported type in Scan: ", reflect.TypeOf(v).String())
} // switch fieldType.Kind() } // switch fieldType.Kind()
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value // !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value
if !hasAssigned { if !hasAssigned {
data, err := value2Bytes(&rawValue) data, err := value2Bytes(&rawValue)
if err == nil { if err != nil {
session.bytes2Value(col, fieldValue, data) return nil, err
} else { }
session.Engine.logger.Error(err.Error())
if err = session.bytes2Value(col, fieldValue, data); err != nil {
return nil, err
} }
} }
} }
@@ -805,19 +816,11 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
return pk, nil return pk, nil
} }
func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) {
for _, filter := range session.Engine.dialect.Filters() {
*sqlStr = filter.Do(*sqlStr, session.Engine.dialect, session.Statement.RefTable)
}
session.saveLastSQL(*sqlStr, paramStr...)
}
// saveLastSQL stores executed query information // saveLastSQL stores executed query information
func (session *Session) saveLastSQL(sql string, args ...interface{}) { func (session *Session) saveLastSQL(sql string, args ...interface{}) {
session.lastSQL = sql session.lastSQL = sql
session.lastSQLArgs = args session.lastSQLArgs = args
session.Engine.logSQL(sql, args...) session.engine.logSQL(sql, args...)
} }
// LastSQL returns last query information // LastSQL returns last query information
@@ -827,8 +830,8 @@ func (session *Session) LastSQL() (string, []interface{}) {
// tbName get some table's table name // tbName get some table's table name
func (session *Session) tbNameNoSchema(table *core.Table) string { func (session *Session) tbNameNoSchema(table *core.Table) string {
if len(session.Statement.AltTableName) > 0 { if len(session.statement.AltTableName) > 0 {
return session.Statement.AltTableName return session.statement.AltTableName
} }
return table.Name return table.Name
@@ -836,6 +839,6 @@ func (session *Session) tbNameNoSchema(table *core.Table) string {
// Unscoped always disable struct tag "deleted" // Unscoped always disable struct tag "deleted"
func (session *Session) Unscoped() *Session { func (session *Session) Unscoped() *Session {
session.Statement.Unscoped() session.statement.Unscoped()
return session return session
} }
+12 -12
View File
@@ -6,43 +6,43 @@ package xorm
// Incr provides a query string like "count = count + 1" // Incr provides a query string like "count = count + 1"
func (session *Session) Incr(column string, arg ...interface{}) *Session { func (session *Session) Incr(column string, arg ...interface{}) *Session {
session.Statement.Incr(column, arg...) session.statement.Incr(column, arg...)
return session return session
} }
// Decr provides a query string like "count = count - 1" // Decr provides a query string like "count = count - 1"
func (session *Session) Decr(column string, arg ...interface{}) *Session { func (session *Session) Decr(column string, arg ...interface{}) *Session {
session.Statement.Decr(column, arg...) session.statement.Decr(column, arg...)
return session return session
} }
// SetExpr provides a query string like "column = {expression}" // SetExpr provides a query string like "column = {expression}"
func (session *Session) SetExpr(column string, expression string) *Session { func (session *Session) SetExpr(column string, expression string) *Session {
session.Statement.SetExpr(column, expression) session.statement.SetExpr(column, expression)
return session return session
} }
// Select provides some columns to special // Select provides some columns to special
func (session *Session) Select(str string) *Session { func (session *Session) Select(str string) *Session {
session.Statement.Select(str) session.statement.Select(str)
return session return session
} }
// Cols provides some columns to special // Cols provides some columns to special
func (session *Session) Cols(columns ...string) *Session { func (session *Session) Cols(columns ...string) *Session {
session.Statement.Cols(columns...) session.statement.Cols(columns...)
return session return session
} }
// AllCols ask all columns // AllCols ask all columns
func (session *Session) AllCols() *Session { func (session *Session) AllCols() *Session {
session.Statement.AllCols() session.statement.AllCols()
return session return session
} }
// MustCols specify some columns must use even if they are empty // MustCols specify some columns must use even if they are empty
func (session *Session) MustCols(columns ...string) *Session { func (session *Session) MustCols(columns ...string) *Session {
session.Statement.MustCols(columns...) session.statement.MustCols(columns...)
return session return session
} }
@@ -52,7 +52,7 @@ func (session *Session) MustCols(columns ...string) *Session {
// If no parameters, it will use all the bool field of struct, or // If no parameters, it will use all the bool field of struct, or
// it will use parameters's columns // it will use parameters's columns
func (session *Session) UseBool(columns ...string) *Session { func (session *Session) UseBool(columns ...string) *Session {
session.Statement.UseBool(columns...) session.statement.UseBool(columns...)
return session return session
} }
@@ -60,25 +60,25 @@ func (session *Session) UseBool(columns ...string) *Session {
// distinct will not be cached because cache system need id, // distinct will not be cached because cache system need id,
// but distinct will not provide id // but distinct will not provide id
func (session *Session) Distinct(columns ...string) *Session { func (session *Session) Distinct(columns ...string) *Session {
session.Statement.Distinct(columns...) session.statement.Distinct(columns...)
return session return session
} }
// Omit Only not use the parameters as select or update columns // Omit Only not use the parameters as select or update columns
func (session *Session) Omit(columns ...string) *Session { func (session *Session) Omit(columns ...string) *Session {
session.Statement.Omit(columns...) session.statement.Omit(columns...)
return session return session
} }
// Nullable Set null when column is zero-value and nullable for update // Nullable Set null when column is zero-value and nullable for update
func (session *Session) Nullable(columns ...string) *Session { func (session *Session) Nullable(columns ...string) *Session {
session.Statement.Nullable(columns...) session.statement.Nullable(columns...)
return session return session
} }
// NoAutoTime means do not automatically give created field and updated field // NoAutoTime means do not automatically give created field and updated field
// the current time on the current session temporarily // the current time on the current session temporarily
func (session *Session) NoAutoTime() *Session { func (session *Session) NoAutoTime() *Session {
session.Statement.UseAutoTime = false session.statement.UseAutoTime = false
return session return session
} }
+9 -9
View File
@@ -17,25 +17,25 @@ func (session *Session) Sql(query string, args ...interface{}) *Session {
// SQL provides raw sql input parameter. When you have a complex SQL statement // SQL provides raw sql input parameter. When you have a complex SQL statement
// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. // and cannot use Where, Id, In and etc. Methods to describe, you can use SQL.
func (session *Session) SQL(query interface{}, args ...interface{}) *Session { func (session *Session) SQL(query interface{}, args ...interface{}) *Session {
session.Statement.SQL(query, args...) session.statement.SQL(query, args...)
return session return session
} }
// Where provides custom query condition. // Where provides custom query condition.
func (session *Session) Where(query interface{}, args ...interface{}) *Session { func (session *Session) Where(query interface{}, args ...interface{}) *Session {
session.Statement.Where(query, args...) session.statement.Where(query, args...)
return session return session
} }
// And provides custom query condition. // And provides custom query condition.
func (session *Session) And(query interface{}, args ...interface{}) *Session { func (session *Session) And(query interface{}, args ...interface{}) *Session {
session.Statement.And(query, args...) session.statement.And(query, args...)
return session return session
} }
// Or provides custom query condition. // Or provides custom query condition.
func (session *Session) Or(query interface{}, args ...interface{}) *Session { func (session *Session) Or(query interface{}, args ...interface{}) *Session {
session.Statement.Or(query, args...) session.statement.Or(query, args...)
return session return session
} }
@@ -48,23 +48,23 @@ func (session *Session) Id(id interface{}) *Session {
// ID provides converting id as a query condition // ID provides converting id as a query condition
func (session *Session) ID(id interface{}) *Session { func (session *Session) ID(id interface{}) *Session {
session.Statement.ID(id) session.statement.ID(id)
return session return session
} }
// In provides a query string like "id in (1, 2, 3)" // In provides a query string like "id in (1, 2, 3)"
func (session *Session) In(column string, args ...interface{}) *Session { func (session *Session) In(column string, args ...interface{}) *Session {
session.Statement.In(column, args...) session.statement.In(column, args...)
return session return session
} }
// NotIn provides a query string like "id in (1, 2, 3)" // NotIn provides a query string like "id in (1, 2, 3)"
func (session *Session) NotIn(column string, args ...interface{}) *Session { func (session *Session) NotIn(column string, args ...interface{}) *Session {
session.Statement.NotIn(column, args...) session.statement.NotIn(column, args...)
return session return session
} }
// Conds returns session query conditions // Conds returns session query conditions except auto bean conditions
func (session *Session) Conds() builder.Cond { func (session *Session) Conds() builder.Cond {
return session.Statement.cond return session.statement.cond
} }
+101 -109
View File
@@ -23,41 +23,38 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti
var x time.Time var x time.Time
var err error var err error
if sdata == "0000-00-00 00:00:00" || var parseLoc = session.engine.DatabaseTZ
sdata == "0001-01-01 00:00:00" { if col.TimeZone != nil {
parseLoc = col.TimeZone
}
if sdata == zeroTime0 || sdata == zeroTime1 {
} else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column } else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column
// time stamp // time stamp
sd, err := strconv.ParseInt(sdata, 10, 64) sd, err := strconv.ParseInt(sdata, 10, 64)
if err == nil { if err == nil {
x = time.Unix(sd, 0) x = time.Unix(sd, 0)
// !nashtsai! HACK mymysql driver is causing Local location being change to CHAT and cause wrong time conversion //session.engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
if col.TimeZone == nil {
x = x.In(session.Engine.TZLocation)
} else {
x = x.In(col.TimeZone)
}
session.Engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else { } else {
session.Engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) //session.engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} }
} else if len(sdata) > 19 && strings.Contains(sdata, "-") { } else if len(sdata) > 19 && strings.Contains(sdata, "-") {
x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation) x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc)
session.Engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) session.engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
if err != nil { if err != nil {
x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation) x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc)
session.Engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) //session.engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} }
if err != nil { if err != nil {
x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation) x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc)
session.Engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) //session.engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} }
} else if len(sdata) == 19 && strings.Contains(sdata, "-") { } else if len(sdata) == 19 && strings.Contains(sdata, "-") {
x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation) x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc)
session.Engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) //session.engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' {
x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation) x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc)
session.Engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) //session.engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else if col.SQLType.Name == core.Time { } else if col.SQLType.Name == core.Time {
if strings.Contains(sdata, " ") { if strings.Contains(sdata, " ") {
ssd := strings.Split(sdata, " ") ssd := strings.Split(sdata, " ")
@@ -65,13 +62,13 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti
} }
sdata = strings.TrimSpace(sdata) sdata = strings.TrimSpace(sdata)
if session.Engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 { if session.engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 {
sdata = sdata[len(sdata)-8:] sdata = sdata[len(sdata)-8:]
} }
st := fmt.Sprintf("2006-01-02 %v", sdata) st := fmt.Sprintf("2006-01-02 %v", sdata)
x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation) x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc)
session.Engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) //session.engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else { } else {
outErr = fmt.Errorf("unsupported time format %v", sdata) outErr = fmt.Errorf("unsupported time format %v", sdata)
return return
@@ -80,7 +77,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti
outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err) outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err)
return return
} }
outTime = x outTime = x.In(session.engine.TZLocation)
return return
} }
@@ -108,7 +105,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 { if len(data) > 0 {
err := json.Unmarshal(data, x.Interface()) err := json.Unmarshal(data, x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return err return err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
@@ -122,7 +119,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 { if len(data) > 0 {
err := json.Unmarshal(data, x.Interface()) err := json.Unmarshal(data, x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return err return err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
@@ -135,7 +132,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 { if len(data) > 0 {
err := json.Unmarshal(data, x.Interface()) err := json.Unmarshal(data, x.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return err return err
} }
fieldValue.Set(x.Elem()) fieldValue.Set(x.Elem())
@@ -147,8 +144,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
case reflect.String: case reflect.String:
fieldValue.SetString(string(data)) fieldValue.SetString(string(data))
case reflect.Bool: case reflect.Bool:
d := string(data) v, err := asBool(data)
v, err := strconv.ParseBool(d)
if err != nil { if err != nil {
return fmt.Errorf("arg %v as bool: %s", key, err.Error()) return fmt.Errorf("arg %v as bool: %s", key, err.Error())
} }
@@ -159,7 +155,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error var err error
// for mysql, when use bit, it returned \x01 // for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit && if col.SQLType.Name == core.Bit &&
session.Engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API session.engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API
if len(data) == 1 { if len(data) == 1 {
x = int64(data[0]) x = int64(data[0])
} else { } else {
@@ -207,41 +203,39 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
} }
v = x v = x
fieldValue.Set(reflect.ValueOf(v).Convert(fieldType)) fieldValue.Set(reflect.ValueOf(v).Convert(fieldType))
} else if session.Statement.UseCascade { } else if session.statement.UseCascade {
table := session.Engine.autoMapType(*fieldValue) table, err := session.engine.autoMapType(*fieldValue)
if table != nil { if err != nil {
// TODO: current only support 1 primary key return err
if len(table.PrimaryKeys) > 1 { }
panic("unsupported composited primary key cascade")
} // TODO: current only support 1 primary key
var pk = make(core.PK, len(table.PrimaryKeys)) if len(table.PrimaryKeys) > 1 {
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) return errors.New("unsupported composited primary key cascade")
var err error }
pk[0], err = str2PK(string(data), rawValueType)
var pk = make(core.PK, len(table.PrimaryKeys))
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
pk[0], err = str2PK(string(data), rawValueType)
if err != nil {
return 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 { if err != nil {
return err return err
} }
if has {
if !isPKZero(pk) { v = structInter.Elem().Interface()
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch fieldValue.Set(reflect.ValueOf(v))
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne } else {
// property to be fetched lazily return errors.New("cascade obj is not exist")
structInter := reflect.New(fieldValue.Type())
newsession := session.Engine.NewSession()
defer newsession.Close()
has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface())
if err != nil {
return err
}
if has {
v = structInter.Elem().Interface()
fieldValue.Set(reflect.ValueOf(v))
} else {
return errors.New("cascade obj is not exist")
}
} }
} else {
return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String())
} }
} }
} }
@@ -267,7 +261,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 { if len(data) > 0 {
err := json.Unmarshal(data, &x) err := json.Unmarshal(data, &x)
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return err return err
} }
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
@@ -278,7 +272,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 { if len(data) > 0 {
err := json.Unmarshal(data, &x) err := json.Unmarshal(data, &x)
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return err return err
} }
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
@@ -350,7 +344,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error var err error
// for mysql, when use bit, it returned \x01 // for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit && if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") { strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 { if len(data) == 1 {
x = int64(data[0]) x = int64(data[0])
} else { } else {
@@ -375,7 +369,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error var err error
// for mysql, when use bit, it returned \x01 // for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit && if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") { strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 { if len(data) == 1 {
x = int(data[0]) x = int(data[0])
} else { } else {
@@ -403,7 +397,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error var err error
// for mysql, when use bit, it returned \x01 // for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit && if col.SQLType.Name == core.Bit &&
session.Engine.dialect.DBType() == core.MYSQL { session.engine.dialect.DBType() == core.MYSQL {
if len(data) == 1 { if len(data) == 1 {
x = int32(data[0]) x = int32(data[0])
} else { } else {
@@ -431,7 +425,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error var err error
// for mysql, when use bit, it returned \x01 // for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit && if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") { strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 { if len(data) == 1 {
x = int8(data[0]) x = int8(data[0])
} else { } else {
@@ -459,7 +453,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error var err error
// for mysql, when use bit, it returned \x01 // for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit && if col.SQLType.Name == core.Bit &&
strings.Contains(session.Engine.DriverName(), "mysql") { strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 { if len(data) == 1 {
x = int16(data[0]) x = int16(data[0])
} else { } else {
@@ -491,37 +485,37 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
v = x v = x
fieldValue.Set(reflect.ValueOf(&x)) fieldValue.Set(reflect.ValueOf(&x))
default: default:
if session.Statement.UseCascade { if session.statement.UseCascade {
structInter := reflect.New(fieldType.Elem()) structInter := reflect.New(fieldType.Elem())
table := session.Engine.autoMapType(structInter.Elem()) table, err := session.engine.autoMapType(structInter.Elem())
if table != nil { if err != nil {
if len(table.PrimaryKeys) > 1 { return err
panic("unsupported composited primary key cascade") }
}
var pk = make(core.PK, len(table.PrimaryKeys)) if len(table.PrimaryKeys) > 1 {
var err error return errors.New("unsupported composited primary key cascade")
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) }
pk[0], err = str2PK(string(data), rawValueType)
var pk = make(core.PK, len(table.PrimaryKeys))
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
pk[0], err = str2PK(string(data), rawValueType)
if err != nil {
return 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
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
if err != nil { if err != nil {
return err return err
} }
if has {
if !isPKZero(pk) { v = structInter.Interface()
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch fieldValue.Set(reflect.ValueOf(v))
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne } else {
// property to be fetched lazily return errors.New("cascade obj is not exist")
newsession := session.Engine.NewSession()
defer newsession.Close()
has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface())
if err != nil {
return err
}
if has {
v = structInter.Interface()
fieldValue.Set(reflect.ValueOf(v))
} else {
return errors.New("cascade obj is not exist")
}
} }
} }
} else { } else {
@@ -570,7 +564,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
if fieldValue.IsNil() { if fieldValue.IsNil() {
return nil, nil return nil, nil
} else if !fieldValue.IsValid() { } else if !fieldValue.IsValid() {
session.Engine.logger.Warn("the field[", col.FieldName, "] is invalid") session.engine.logger.Warn("the field[", col.FieldName, "] is invalid")
return nil, nil return nil, nil
} else { } else {
// !nashtsai! deference pointer type to instance type // !nashtsai! deference pointer type to instance type
@@ -588,12 +582,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
case reflect.Struct: case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) { if fieldType.ConvertibleTo(core.TimeType) {
t := fieldValue.Convert(core.TimeType).Interface().(time.Time) t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
if session.Engine.dialect.DBType() == core.MSSQL { tf := session.engine.formatColTime(col, t)
if t.IsZero() {
return nil, nil
}
}
tf := session.Engine.FormatTime(col.SQLType.Name, t)
return tf, nil return tf, nil
} }
@@ -603,7 +592,10 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
return v.Value() return v.Value()
} }
fieldTable := session.Engine.autoMapType(fieldValue) fieldTable, err := session.engine.autoMapType(fieldValue)
if err != nil {
return nil, err
}
if len(fieldTable.PrimaryKeys) == 1 { if len(fieldTable.PrimaryKeys) == 1 {
pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName) pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName)
return pkField.Interface(), nil return pkField.Interface(), nil
@@ -614,14 +606,14 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
if col.SQLType.IsText() { if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface()) bytes, err := json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return 0, err return 0, err
} }
return string(bytes), nil return string(bytes), nil
} else if col.SQLType.IsBlob() { } else if col.SQLType.IsBlob() {
bytes, err := json.Marshal(fieldValue.Interface()) bytes, err := json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return 0, err return 0, err
} }
return bytes, nil return bytes, nil
@@ -630,7 +622,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
case reflect.Complex64, reflect.Complex128: case reflect.Complex64, reflect.Complex128:
bytes, err := json.Marshal(fieldValue.Interface()) bytes, err := json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return 0, err return 0, err
} }
return string(bytes), nil return string(bytes), nil
@@ -642,7 +634,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
if col.SQLType.IsText() { if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface()) bytes, err := json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return 0, err return 0, err
} }
return string(bytes), nil return string(bytes), nil
@@ -655,7 +647,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
} else { } else {
bytes, err = json.Marshal(fieldValue.Interface()) bytes, err = json.Marshal(fieldValue.Interface())
if err != nil { if err != nil {
session.Engine.logger.Error(err) session.engine.logger.Error(err)
return 0, err return 0, err
} }
} }
+40 -38
View File
@@ -12,26 +12,26 @@ import (
"github.com/go-xorm/core" "github.com/go-xorm/core"
) )
func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, args ...interface{}) error {
if session.Statement.RefTable == nil || if table == nil ||
session.Tx != nil { session.tx != nil {
return ErrCacheFailed return ErrCacheFailed
} }
for _, filter := range session.Engine.dialect.Filters() { for _, filter := range session.engine.dialect.Filters() {
sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) sqlStr = filter.Do(sqlStr, session.engine.dialect, table)
} }
newsql := session.Statement.convertIDSQL(sqlStr) newsql := session.statement.convertIDSQL(sqlStr)
if newsql == "" { if newsql == "" {
return ErrCacheFailed return ErrCacheFailed
} }
cacher := session.Engine.getCacher2(session.Statement.RefTable) cacher := session.engine.getCacher2(table)
tableName := session.Statement.TableName() pkColumns := table.PKColumns()
ids, err := core.GetCacheSql(cacher, tableName, newsql, args) ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil { if err != nil {
resultsSlice, err := session.query(newsql, args...) resultsSlice, err := session.queryBytes(newsql, args...)
if err != nil { if err != nil {
return err return err
} }
@@ -40,7 +40,7 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error {
for _, data := range resultsSlice { for _, data := range resultsSlice {
var id int64 var id int64
var pk core.PK = make([]interface{}, 0) var pk core.PK = make([]interface{}, 0)
for _, col := range session.Statement.RefTable.PKColumns() { for _, col := range pkColumns {
if v, ok := data[col.Name]; !ok { if v, ok := data[col.Name]; !ok {
return errors.New("no id") return errors.New("no id")
} else if col.SQLType.IsText() { } else if col.SQLType.IsText() {
@@ -58,33 +58,30 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error {
ids = append(ids, pk) ids = append(ids, pk)
} }
} }
} /*else { }
session.Engine.LogDebug("delete cache sql %v", newsql)
cacher.DelIds(tableName, genSqlKey(newsql, args))
}*/
for _, id := range ids { for _, id := range ids {
session.Engine.logger.Debug("[cacheDelete] delete cache obj", tableName, id) session.engine.logger.Debug("[cacheDelete] delete cache obj:", tableName, id)
sid, err := id.ToString() sid, err := id.ToString()
if err != nil { if err != nil {
return err return err
} }
cacher.DelBean(tableName, sid) cacher.DelBean(tableName, sid)
} }
session.Engine.logger.Debug("[cacheDelete] clear cache sql", tableName) session.engine.logger.Debug("[cacheDelete] clear cache table:", tableName)
cacher.ClearIds(tableName) cacher.ClearIds(tableName)
return nil return nil
} }
// Delete records, bean's non-empty fields are conditions // Delete records, bean's non-empty fields are conditions
func (session *Session) Delete(bean interface{}) (int64, error) { func (session *Session) Delete(bean interface{}) (int64, error) {
defer session.resetStatement() if session.isAutoClose {
if session.IsAutoClose {
defer session.Close() defer session.Close()
} }
session.Statement.setRefValue(rValue(bean)) if err := session.statement.setRefValue(rValue(bean)); err != nil {
var table = session.Statement.RefTable return 0, err
}
// handle before delete processors // handle before delete processors
for _, closure := range session.beforeClosures { for _, closure := range session.beforeClosures {
@@ -96,13 +93,17 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
processor.BeforeDelete() processor.BeforeDelete()
} }
// -- condSQL, condArgs, err := session.statement.genConds(bean)
condSQL, condArgs, _ := session.Statement.genConds(bean) if err != nil {
if len(condSQL) == 0 && session.Statement.LimitN == 0 { return 0, err
}
if len(condSQL) == 0 && session.statement.LimitN == 0 {
return 0, ErrNeedDeletedCond return 0, ErrNeedDeletedCond
} }
var tableName = session.Engine.Quote(session.Statement.TableName()) var tableNameNoQuote = session.statement.TableName()
var tableName = session.engine.Quote(tableNameNoQuote)
var table = session.statement.RefTable
var deleteSQL string var deleteSQL string
if len(condSQL) > 0 { if len(condSQL) > 0 {
deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL) deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL)
@@ -111,15 +112,15 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
} }
var orderSQL string var orderSQL string
if len(session.Statement.OrderStr) > 0 { if len(session.statement.OrderStr) > 0 {
orderSQL += fmt.Sprintf(" ORDER BY %s", session.Statement.OrderStr) orderSQL += fmt.Sprintf(" ORDER BY %s", session.statement.OrderStr)
} }
if session.Statement.LimitN > 0 { if session.statement.LimitN > 0 {
orderSQL += fmt.Sprintf(" LIMIT %d", session.Statement.LimitN) orderSQL += fmt.Sprintf(" LIMIT %d", session.statement.LimitN)
} }
if len(orderSQL) > 0 { if len(orderSQL) > 0 {
switch session.Engine.dialect.DBType() { switch session.engine.dialect.DBType() {
case core.POSTGRES: case core.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 { if len(condSQL) > 0 {
@@ -144,7 +145,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
var realSQL string var realSQL string
argsForCache := make([]interface{}, 0, len(condArgs)*2) argsForCache := make([]interface{}, 0, len(condArgs)*2)
if session.Statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled if session.statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled
realSQL = deleteSQL realSQL = deleteSQL
copy(argsForCache, condArgs) copy(argsForCache, condArgs)
argsForCache = append(condArgs, argsForCache...) argsForCache = append(condArgs, argsForCache...)
@@ -155,12 +156,12 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
deletedColumn := table.DeletedColumn() deletedColumn := table.DeletedColumn()
realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v",
session.Engine.Quote(session.Statement.TableName()), session.engine.Quote(session.statement.TableName()),
session.Engine.Quote(deletedColumn.Name), session.engine.Quote(deletedColumn.Name),
condSQL) condSQL)
if len(orderSQL) > 0 { if len(orderSQL) > 0 {
switch session.Engine.dialect.DBType() { switch session.engine.dialect.DBType() {
case core.POSTGRES: case core.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 { if len(condSQL) > 0 {
@@ -183,12 +184,12 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
} }
} }
// !oinume! Insert NowTime to the head of session.Statement.Params // !oinume! Insert nowTime to the head of session.statement.Params
condArgs = append(condArgs, "") condArgs = append(condArgs, "")
paramsLen := len(condArgs) paramsLen := len(condArgs)
copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1]) copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1])
val, t := session.Engine.NowTime2(deletedColumn.SQLType.Name) val, t := session.engine.nowTime(deletedColumn)
condArgs[0] = val condArgs[0] = val
var colName = deletedColumn.Name var colName = deletedColumn.Name
@@ -198,17 +199,18 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
}) })
} }
if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && session.Statement.UseCache { if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
session.cacheDelete(deleteSQL, argsForCache...) session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...)
} }
session.statement.RefTable = table
res, err := session.exec(realSQL, condArgs...) res, err := session.exec(realSQL, condArgs...)
if err != nil { if err != nil {
return 0, err return 0, err
} }
// handle after delete processors // handle after delete processors
if session.IsAutoCommit { if session.isAutoCommit {
for _, closure := range session.afterClosures { for _, closure := range session.afterClosures {
closure(bean) closure(bean)
} }

Some files were not shown because too many files have changed in this diff Show More