Commit afb847ac by Torkel Ödegaard

a lot of work on database schema and migration setup, postgres now works, every…

a lot of work on database schema and migration setup, postgres now works, every integration test passes for all database types, only token table left to do
parent 8bb9126b
...@@ -33,6 +33,8 @@ type DataSource struct { ...@@ -33,6 +33,8 @@ type DataSource struct {
User string User string
Database string Database string
BasicAuth bool BasicAuth bool
BasicAuthUser string
BasicAuthPassword string
IsDefault bool IsDefault bool
Created time.Time Created time.Time
......
...@@ -81,7 +81,7 @@ func TestAccountDataAccess(t *testing.T) { ...@@ -81,7 +81,7 @@ func TestAccountDataAccess(t *testing.T) {
err := SetUsingAccount(&cmd) err := SetUsingAccount(&cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Logged in user query should return correct using account info", func() { Convey("SignedInUserQuery with a different account", func() {
query := m.GetSignedInUserQuery{UserId: ac2.Id} query := m.GetSignedInUserQuery{UserId: ac2.Id}
err := GetSignedInUser(&query) err := GetSignedInUser(&query)
......
...@@ -44,11 +44,11 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error { ...@@ -44,11 +44,11 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
// insert new tags // insert new tags
tags := dash.GetTags() tags := dash.GetTags()
if len(tags) > 0 { if len(tags) > 0 {
tagRows := make([]DashboardTag, len(tags))
for _, tag := range tags { for _, tag := range tags {
tagRows = append(tagRows, DashboardTag{Term: tag, DashboardId: dash.Id}) if _, err := sess.Insert(&DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
return err
}
} }
sess.InsertMulti(&tagRows)
} }
cmd.Result = dash cmd.Result = dash
...@@ -120,8 +120,7 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error { ...@@ -120,8 +120,7 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error {
} }
func GetDashboardTags(query *m.GetDashboardTagsQuery) error { func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
sess := x.Sql("select count() as count, term from dashboard_tag group by term") sess := x.Sql("select count(*) as count, term from dashboard_tag group by term")
err := sess.Find(&query.Result) err := sess.Find(&query.Result)
return err return err
} }
......
...@@ -95,7 +95,7 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -95,7 +95,7 @@ func TestDashboardDataAccess(t *testing.T) {
err := GetDashboardTags(&query) err := GetDashboardTags(&query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3) So(len(query.Result), ShouldEqual, 2)
}) })
}) })
}) })
......
...@@ -8,16 +8,24 @@ import ( ...@@ -8,16 +8,24 @@ import (
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
m "github.com/torkelo/grafana-pro/pkg/models" m "github.com/torkelo/grafana-pro/pkg/models"
"github.com/torkelo/grafana-pro/pkg/services/sqlstore/sqlutil"
) )
func InitTestDB(t *testing.T) { func InitTestDB(t *testing.T) {
x, err := xorm.NewEngine("sqlite3", ":memory:")
x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
//x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
//x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
if err != nil { if err != nil {
t.Fatalf("Failed to init in memory sqllite3 db %v", err) t.Fatalf("Failed to init in memory sqllite3 db %v", err)
} }
SetEngine(x, false) sqlutil.CleanDB(x)
if err := SetEngine(x, false); err != nil {
t.Fatal(err)
}
} }
type Test struct { type Test struct {
......
package migrations package sqlstore
import "time" import . "github.com/torkelo/grafana-pro/pkg/services/sqlstore/migrator"
func AddMigrations(mg *Migrator) { func addMigrations(mg *Migrator) {
addMigrationLogMigrations(mg) addMigrationLogMigrations(mg)
addUserMigrations(mg) addUserMigrations(mg)
addAccountMigrations(mg) addAccountMigrations(mg)
addDashboardMigration(mg) addDashboardMigration(mg)
addDataSourceMigration(mg)
} }
func addMigrationLogMigrations(mg *Migrator) { func addMigrationLogMigrations(mg *Migrator) {
...@@ -38,10 +39,10 @@ func addUserMigrations(mg *Migrator) { ...@@ -38,10 +39,10 @@ func addUserMigrations(mg *Migrator) {
)) ))
//------- user table indexes ------------------ //------- user table indexes ------------------
mg.AddMigration("add unique index UIX_user.login", new(AddIndexMigration). mg.AddMigration("add unique index user.login", new(AddIndexMigration).
Name("UIX_user_login").Table("user").Columns("login")) Table("user").Columns("login").Unique())
mg.AddMigration("add unique index UIX_user.email", new(AddIndexMigration). mg.AddMigration("add unique index user.email", new(AddIndexMigration).
Name("UIX_user_email").Table("user").Columns("email")) Table("user").Columns("email").Unique())
} }
func addAccountMigrations(mg *Migrator) { func addAccountMigrations(mg *Migrator) {
...@@ -53,8 +54,8 @@ func addAccountMigrations(mg *Migrator) { ...@@ -53,8 +54,8 @@ func addAccountMigrations(mg *Migrator) {
&Column{Name: "updated", Type: DB_DateTime, Nullable: false}, &Column{Name: "updated", Type: DB_DateTime, Nullable: false},
)) ))
mg.AddMigration("add unique index UIX_account.name", new(AddIndexMigration). mg.AddMigration("add unique index account.name", new(AddIndexMigration).
Name("UIX_account_name").Table("account").Columns("name")) Table("account").Columns("name").Unique())
//------- account_user table ------------------- //------- account_user table -------------------
mg.AddMigration("create account_user table", new(AddTableMigration). mg.AddMigration("create account_user table", new(AddTableMigration).
...@@ -67,20 +68,8 @@ func addAccountMigrations(mg *Migrator) { ...@@ -67,20 +68,8 @@ func addAccountMigrations(mg *Migrator) {
&Column{Name: "updated", Type: DB_DateTime}, &Column{Name: "updated", Type: DB_DateTime},
)) ))
mg.AddMigration("add unique index UIX_account_user", new(AddIndexMigration). mg.AddMigration("add unique index account_user_aid_uid", new(AddIndexMigration).
Name("UIX_account_user").Table("account_user").Columns("account_id", "user_id")) Name("aid_uid").Table("account_user").Columns("account_id", "user_id").Unique())
}
type Dashboard struct {
Id int64
Slug string `xorm:"index(IX_AccountIdSlug)"`
AccountId int64 `xorm:"index(IX_AccountIdSlug)"`
Created time.Time
Updated time.Time
Title string
Data map[string]interface{}
} }
func addDashboardMigration(mg *Migrator) { func addDashboardMigration(mg *Migrator) {
...@@ -95,10 +84,48 @@ func addDashboardMigration(mg *Migrator) { ...@@ -95,10 +84,48 @@ func addDashboardMigration(mg *Migrator) {
&Column{Name: "updated", Type: DB_DateTime, Nullable: false}, &Column{Name: "updated", Type: DB_DateTime, Nullable: false},
)) ))
mg.AddMigration("create dashboard_tag table", new(AddTableMigration).
Name("dashboard_tag").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
))
//------- indexes ------------------
mg.AddMigration("add index dashboard.account_id", new(AddIndexMigration).
Table("dashboard").Columns("account_id"))
mg.AddMigration("add unique index dashboard_account_id_slug", new(AddIndexMigration).
Table("dashboard").Columns("account_id", "slug").Unique())
mg.AddMigration("add unique index dashboard_tag.dasboard_id_term", new(AddIndexMigration).
Table("dashboard_tag").Columns("dashboard_id", "term").Unique())
}
func addDataSourceMigration(mg *Migrator) {
mg.AddMigration("create data_source table", new(AddTableMigration).
Name("data_source").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth", Type: DB_Bool, Nullable: false},
&Column{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "is_default", Type: DB_Bool, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
))
//------- indexes ------------------ //------- indexes ------------------
mg.AddMigration("add unique index UIX_dashboard.account_id", new(AddIndexMigration). mg.AddMigration("add index data_source.account_id", new(AddIndexMigration).
Name("UIX_dashboard_account_id").Table("dashboard").Columns("account_id")) Table("data_source").Columns("account_id"))
mg.AddMigration("add unique index UIX_dashboard_account_id_slug", new(AddIndexMigration). mg.AddMigration("add unique index data_source.account_id_name", new(AddIndexMigration).
Name("UIX_dashboard_account_id_slug").Table("dashboard").Columns("account_id", "slug")) Table("data_source").Columns("account_id", "name").Unique())
} }
package migrations package sqlstore
import ( import (
"fmt" "fmt"
...@@ -7,52 +7,34 @@ import ( ...@@ -7,52 +7,34 @@ import (
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
"github.com/torkelo/grafana-pro/pkg/log" "github.com/torkelo/grafana-pro/pkg/log"
. "github.com/torkelo/grafana-pro/pkg/services/sqlstore/migrator"
"github.com/torkelo/grafana-pro/pkg/services/sqlstore/sqlutil"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
func cleanDB(x *xorm.Engine) { var indexTypes = []string{"Unknown", "INDEX", "UNIQUE INDEX"}
tables, _ := x.DBMetas()
sess := x.NewSession()
defer sess.Close()
for _, table := range tables { func ATestMigrations(t *testing.T) {
if _, err := sess.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
panic("Failed to disable foreign key checks")
}
if _, err := sess.Exec("DROP TABLE " + table.Name); err != nil {
panic(fmt.Sprintf("Failed to delete table: %v, err: %v", table.Name, err))
}
if _, err := sess.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
panic("Failed to disable foreign key checks")
}
}
}
var indexTypes = []string{"Unknown", "", "UNIQUE"}
func TestMigrations(t *testing.T) {
log.NewLogger(0, "console", `{"level": 0}`) log.NewLogger(0, "console", `{"level": 0}`)
testDBs := [][]string{ testDBs := []sqlutil.TestDB{
[]string{"postgres", "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}, sqlutil.TestDB_Sqlite3,
[]string{"mysql", "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8"}, sqlutil.TestDB_Mysql,
[]string{"sqlite3", ":memory:"}, sqlutil.TestDB_Postgres,
} }
for _, testDB := range testDBs { for _, testDB := range testDBs {
Convey("Initial "+testDB[0]+" migration", t, func() { Convey("Initial "+testDB.DriverName+" migration", t, func() {
x, err := xorm.NewEngine(testDB[0], testDB[1]) x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr)
So(err, ShouldBeNil) So(err, ShouldBeNil)
if testDB[0] == "mysql" { sqlutil.CleanDB(x)
cleanDB(x)
}
mg := NewMigrator(x) mg := NewMigrator(x)
mg.LogLevel = log.DEBUG mg.LogLevel = log.DEBUG
AddMigrations(mg) addMigrations(mg)
err = mg.Start() err = mg.Start()
So(err, ShouldBeNil) So(err, ShouldBeNil)
......
package migrations package migrator
import (
"fmt"
"strings"
)
type MigrationBase struct { type MigrationBase struct {
id string id string
...@@ -87,6 +92,9 @@ func (m *AddIndexMigration) Columns(columns ...string) *AddIndexMigration { ...@@ -87,6 +92,9 @@ func (m *AddIndexMigration) Columns(columns ...string) *AddIndexMigration {
} }
func (m *AddIndexMigration) Sql(dialect Dialect) string { func (m *AddIndexMigration) Sql(dialect Dialect) string {
if m.index.Name == "" {
m.index.Name = fmt.Sprintf("%s", strings.Join(m.index.Cols, "_"))
}
return dialect.CreateIndexSql(m.tableName, &m.index) return dialect.CreateIndexSql(m.tableName, &m.index)
} }
......
package migrations package migrator
// Notice // Notice
// code based on parts from from https://github.com/go-xorm/core/blob/3e0fa232ab5c90996406c0cd7ae86ad0e5ecf85f/column.go // code based on parts from from https://github.com/go-xorm/core/blob/3e0fa232ab5c90996406c0cd7ae86ad0e5ecf85f/column.go
......
package migrations package migrator
import ( import (
"fmt" "fmt"
...@@ -23,6 +23,19 @@ type Dialect interface { ...@@ -23,6 +23,19 @@ type Dialect interface {
TableCheckSql(tableName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{})
} }
func NewDialect(name string) Dialect {
switch name {
case MYSQL:
return NewMysqlDialect()
case SQLITE:
return NewSqlite3Dialect()
case POSTGRES:
return NewPostgresDialect()
}
panic("Unsupported database type: " + name)
}
type BaseDialect struct { type BaseDialect struct {
dialect Dialect dialect Dialect
driverName string driverName string
......
package migrations package migrator
import ( import (
"time" "time"
...@@ -32,16 +32,7 @@ func NewMigrator(engine *xorm.Engine) *Migrator { ...@@ -32,16 +32,7 @@ func NewMigrator(engine *xorm.Engine) *Migrator {
mg.x = engine mg.x = engine
mg.LogLevel = log.WARN mg.LogLevel = log.WARN
mg.migrations = make([]Migration, 0) mg.migrations = make([]Migration, 0)
mg.dialect = NewDialect(mg.x.DriverName())
switch mg.x.DriverName() {
case MYSQL:
mg.dialect = NewMysqlDialect()
case SQLITE:
mg.dialect = NewSqlite3Dialect()
case POSTGRES:
mg.dialect = NewPostgresDialect()
}
return mg return mg
} }
......
package migrations package migrator
import ( import "strconv"
"strconv"
"github.com/go-xorm/core"
)
type Postgres struct { type Postgres struct {
BaseDialect BaseDialect
...@@ -35,16 +31,16 @@ func (db *Postgres) SqlType(c *Column) string { ...@@ -35,16 +31,16 @@ func (db *Postgres) SqlType(c *Column) string {
case DB_TinyInt: case DB_TinyInt:
res = DB_SmallInt res = DB_SmallInt
return res return res
case DB_MediumInt, core.Int, core.Integer: case DB_MediumInt, DB_Int, DB_Integer:
if c.IsAutoIncrement { if c.IsAutoIncrement {
return DB_Serial return DB_Serial
} }
return DB_Integer return DB_Integer
case DB_Serial, core.BigSerial: case DB_Serial, DB_BigSerial:
c.IsAutoIncrement = true c.IsAutoIncrement = true
c.Nullable = false c.Nullable = false
res = t res = t
case DB_Binary, core.VarBinary: case DB_Binary, DB_VarBinary:
return DB_Bytea return DB_Bytea
case DB_DateTime: case DB_DateTime:
res = DB_TimeStamp res = DB_TimeStamp
...@@ -52,13 +48,13 @@ func (db *Postgres) SqlType(c *Column) string { ...@@ -52,13 +48,13 @@ func (db *Postgres) SqlType(c *Column) string {
return "timestamp with time zone" return "timestamp with time zone"
case DB_Float: case DB_Float:
res = DB_Real res = DB_Real
case DB_TinyText, core.MediumText, core.LongText: case DB_TinyText, DB_MediumText, DB_LongText:
res = DB_Text res = DB_Text
case DB_NVarchar: case DB_NVarchar:
res = DB_Varchar res = DB_Varchar
case DB_Uuid: case DB_Uuid:
res = DB_Uuid res = DB_Uuid
case DB_Blob, core.TinyBlob, core.MediumBlob, core.LongBlob: case DB_Blob, DB_TinyBlob, DB_MediumBlob, DB_LongBlob:
return DB_Bytea return DB_Bytea
case DB_Double: case DB_Double:
return "DOUBLE PRECISION" return "DOUBLE PRECISION"
......
package migrations package migrator
import "github.com/go-xorm/core"
type Sqlite3 struct { type Sqlite3 struct {
BaseDialect BaseDialect
...@@ -32,7 +30,7 @@ func (db *Sqlite3) SqlType(c *Column) string { ...@@ -32,7 +30,7 @@ func (db *Sqlite3) SqlType(c *Column) string {
case DB_TimeStampz: case DB_TimeStampz:
return DB_Text return DB_Text
case DB_Char, DB_Varchar, DB_NVarchar, DB_TinyText, DB_Text, DB_MediumText, DB_LongText: case DB_Char, DB_Varchar, DB_NVarchar, DB_TinyText, DB_Text, DB_MediumText, DB_LongText:
return core.Text return DB_Text
case DB_Bit, DB_TinyInt, DB_SmallInt, DB_MediumInt, DB_Int, DB_Integer, DB_BigInt, DB_Bool: case DB_Bit, DB_TinyInt, DB_SmallInt, DB_MediumInt, DB_Int, DB_Integer, DB_BigInt, DB_Bool:
return DB_Integer return DB_Integer
case DB_Float, DB_Double, DB_Real: case DB_Float, DB_Double, DB_Real:
...@@ -45,7 +43,7 @@ func (db *Sqlite3) SqlType(c *Column) string { ...@@ -45,7 +43,7 @@ func (db *Sqlite3) SqlType(c *Column) string {
c.IsPrimaryKey = true c.IsPrimaryKey = true
c.IsAutoIncrement = true c.IsAutoIncrement = true
c.Nullable = false c.Nullable = false
return core.Integer return DB_Integer
default: default:
return c.Type return c.Type
} }
......
package migrations package migrator
const ( const (
POSTGRES = "postgres" POSTGRES = "postgres"
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/torkelo/grafana-pro/pkg/bus" "github.com/torkelo/grafana-pro/pkg/bus"
"github.com/torkelo/grafana-pro/pkg/log" "github.com/torkelo/grafana-pro/pkg/log"
m "github.com/torkelo/grafana-pro/pkg/models" m "github.com/torkelo/grafana-pro/pkg/models"
"github.com/torkelo/grafana-pro/pkg/services/sqlstore/migrations" "github.com/torkelo/grafana-pro/pkg/services/sqlstore/migrator"
"github.com/torkelo/grafana-pro/pkg/setting" "github.com/torkelo/grafana-pro/pkg/setting"
"github.com/torkelo/grafana-pro/pkg/util" "github.com/torkelo/grafana-pro/pkg/util"
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
var ( var (
x *xorm.Engine x *xorm.Engine
dialect migrator.Dialect
tables []interface{} tables []interface{}
HasEngine bool HasEngine bool
...@@ -34,9 +35,7 @@ var ( ...@@ -34,9 +35,7 @@ var (
func init() { func init() {
tables = make([]interface{}, 0) tables = make([]interface{}, 0)
tables = append(tables, new(m.Token))
tables = append(tables, new(m.DataSource), new(DashboardTag),
new(m.Token))
} }
func EnsureAdminUser() { func EnsureAdminUser() {
...@@ -76,9 +75,10 @@ func NewEngine() { ...@@ -76,9 +75,10 @@ func NewEngine() {
func SetEngine(engine *xorm.Engine, enableLog bool) (err error) { func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
x = engine x = engine
dialect = migrator.NewDialect(x.DriverName())
migrator := migrations.NewMigrator(x) migrator := migrator.NewMigrator(x)
migrations.AddMigrations(migrator) addMigrations(migrator)
if err := migrator.Start(); err != nil { if err := migrator.Start(); err != nil {
return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err) return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
......
package sqlutil
import (
"fmt"
"github.com/go-xorm/xorm"
)
type TestDB struct {
DriverName string
ConnStr string
}
var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:"}
var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8"}
var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}
func CleanDB(x *xorm.Engine) {
if x.DriverName() == "postgres" {
sess := x.NewSession()
defer sess.Close()
if _, err := sess.Exec("DROP SCHEMA public CASCADE;"); err != nil {
panic("Failed to drop schema public")
}
if _, err := sess.Exec("CREATE SCHEMA public;"); err != nil {
panic("Failed to create schema public")
}
} else if x.DriverName() == "mysql" {
tables, _ := x.DBMetas()
sess := x.NewSession()
defer sess.Close()
for _, table := range tables {
if _, err := sess.Exec("set foreign_key_checks = 0"); err != nil {
panic("failed to disable foreign key checks")
}
if _, err := sess.Exec("drop table " + table.Name + " ;"); err != nil {
panic(fmt.Sprintf("failed to delete table: %v, err: %v", table.Name, err))
}
if _, err := sess.Exec("set foreign_key_checks = 1"); err != nil {
panic("failed to disable foreign key checks")
}
}
}
}
...@@ -150,18 +150,18 @@ func GetUserAccounts(query *m.GetUserAccountsQuery) error { ...@@ -150,18 +150,18 @@ func GetUserAccounts(query *m.GetUserAccountsQuery) error {
func GetSignedInUser(query *m.GetSignedInUserQuery) error { func GetSignedInUser(query *m.GetSignedInUserQuery) error {
var rawSql = `SELECT var rawSql = `SELECT
user.id as user_id, u.id as user_id,
user.is_admin as is_grafana_admin, u.is_admin as is_grafana_admin,
user.email as email, u.email as email,
user.login as login, u.login as login,
user.name as name, u.name as name,
account.name as account_name, account.name as account_name,
account_user.role as account_role, account_user.role as account_role,
account.id as account_id account.id as account_id
FROM user FROM ` + dialect.Quote("user") + ` as u
LEFT OUTER JOIN account_user on account_user.account_id = user.account_id and account_user.user_id = user.id LEFT OUTER JOIN account_user on account_user.account_id = u.account_id and account_user.user_id = u.id
LEFT OUTER JOIN account on account.id = user.account_id LEFT OUTER JOIN account on account.id = u.account_id
WHERE user.id=?` WHERE u.id=?`
var user m.SignedInUser var user m.SignedInUser
sess := x.Table("user") sess := x.Table("user")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment