3
0

Fix and extend RDBMS upgrade procdures

This commit is contained in:
Denis Arh
2022-06-29 15:46:37 +02:00
parent 61b91a8a6a
commit 8cbb2026e0
17 changed files with 354 additions and 144 deletions
+4 -5
View File
@@ -202,20 +202,19 @@ func (app *CortezaApp) InitStore(ctx context.Context) (err error) {
}
}
app.Log.Info("running store update")
if !app.Opt.Upgrade.Always {
app.Log.Info("store upgrade skipped (UPGRADE_ALWAYS=false)")
} else {
log := app.Log.Named("store")
log.Info("running schema upgrade")
ctx = actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_APP_Upgrade)
// If not explicitly set (UPGRADE_DEBUG=true) suppress logging in upgrader
log := zap.NewNop()
if app.Opt.Upgrade.Debug {
log = app.Log.Named("store.upgrade")
log.Info("store upgrade running in debug mode (UPGRADE_DEBUG=true)")
} else {
app.Log.Info("store upgrade running (to enable upgrade debug logging set UPGRADE_DEBUG=true)")
log.Info("store upgrade running (to enable upgrade debug logging set UPGRADE_DEBUG=true)")
log = zap.NewNop()
}
if err = store.Upgrade(ctx, log, app.Store); err != nil {
+2
View File
@@ -34,6 +34,8 @@ func Connect(ctx context.Context, log *zap.Logger, cfg *ConnConfig) (db *sqlx.DB
base *sql.DB
)
log = log.Named("store")
if base, err = sql.Open(cfg.DriverName, cfg.DataSourceName); err != nil {
return
}
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/drivers"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/jmoiron/sqlx"
@@ -13,27 +12,43 @@ import (
type (
trColTypeFn func(ColumnType) string
CreateTableTemplate struct {
*Table
OmitIfNotExistsClause bool
SuffixClause string
TrColumnTypes trColTypeFn
dialect interface {
// GOQU returns goqu's dialect wrapper struct
GOQU() goqu.DialectWrapper
NativeColumnType(columnType ColumnType) string
}
CreateIndexTemplate struct {
*Index
CreateTable struct {
Dialect dialect
Table *Table
OmitIfNotExistsClause bool
SuffixClause string
}
CreateIndex struct {
Dialect dialect
Index *Index
OmitIfNotExistsClause bool
OmitFieldLength bool
}
AddColumn struct {
Dialect dialect
Table *Table
Column *Column
}
)
func CreateIndexTemplates(base *CreateIndexTemplate, ii ...*Index) []any {
func CreateIndexTemplates(base *CreateIndex, ii ...*Index) []any {
var (
tt = make([]any, len(ii))
)
for i := range ii {
tt[i] = &CreateIndexTemplate{
tt[i] = &CreateIndex{
Index: ii[i],
OmitIfNotExistsClause: base.OmitIfNotExistsClause,
OmitFieldLength: base.OmitFieldLength,
@@ -43,8 +58,12 @@ func CreateIndexTemplates(base *CreateIndexTemplate, ii ...*Index) []any {
return tt
}
// utility for executing series fo commands
func Exec(ctx context.Context, db sqlx.ExecerContext, ss ...any) (err error) {
// Exec is an utility for executing series of commands
//
// Parameters can be string, Stringer interface or goqu's exp.SQLExpression
//
// Any other type will result in panic
func Exec(ctx context.Context, db sqlx.ExtContext, ss ...any) (err error) {
for _, s := range ss {
var (
sql string
@@ -58,6 +77,9 @@ func Exec(ctx context.Context, db sqlx.ExecerContext, ss ...any) (err error) {
sql = c.String()
case exp.SQLExpression:
sql, args, err = c.ToSQL()
if err != nil {
return
}
default:
panic(fmt.Sprintf("unexecutable input (%T)", s))
}
@@ -70,11 +92,15 @@ func Exec(ctx context.Context, db sqlx.ExecerContext, ss ...any) (err error) {
return
}
func TableExists(ctx context.Context, db sqlx.QueryerContext, d drivers.Dialect, table, schema string) (bool, error) {
func TableExists(ctx context.Context, db sqlx.QueryerContext, d dialect, table, schema string) (bool, error) {
return GetBool(ctx, db, GenTableCheck(d, table, schema))
}
func GenTableCheck(d drivers.Dialect, table, schema string) *goqu.SelectDataset {
func ColumnExists(ctx context.Context, db sqlx.QueryerContext, d dialect, column, table, schema string) (bool, error) {
return GetBool(ctx, db, GenColumnCheck(d, column, table, schema))
}
func GenTableCheck(d dialect, table, schema string) *goqu.SelectDataset {
return d.GOQU().Select(goqu.COUNT(goqu.Star()).Gt(0)).
From("information_schema.tables").
Where(
@@ -83,11 +109,47 @@ func GenTableCheck(d drivers.Dialect, table, schema string) *goqu.SelectDataset
)
}
func IndexExists(ctx context.Context, db sqlx.QueryerContext, d drivers.Dialect, index, table, schema string) (bool, error) {
func GenColumnCheck(d dialect, column, table, schema string) *goqu.SelectDataset {
return d.GOQU().Select(goqu.COUNT(goqu.Star()).Gt(0)).
From("information_schema.columns").
Where(
exp.ParseIdentifier("table_name").Eq(table),
exp.ParseIdentifier("table_schema").Eq(schema),
exp.ParseIdentifier("column_name").Eq(column),
)
}
//
//func GenAddColumn(ctx context.Context, dialect dialect, db sqlx.ExtContext, t *Table, cc ...*Column) (err error) {
// var (
// aux []any
// exists bool
// )
//
// for _, c := range cc {
// // check column existence
// if exists, err = ColumnExists(ctx, db, dialect, c.Name, t.Name, "public"); err != nil {
// return
// } else if exists {
// // column exists
// continue
// }
//
// aux = append(aux, &AddColumn{
// Dialect: dialect,
// Table: t,
// Column: c,
// })
// }
//
// return Exec(ctx, db, aux...)
//}
func IndexExists(ctx context.Context, db sqlx.QueryerContext, d dialect, index, table, schema string) (bool, error) {
return GetBool(ctx, db, GenIndexCheck(d, index, table, schema))
}
func GenIndexCheck(d drivers.Dialect, index, table, schema string) *goqu.SelectDataset {
func GenIndexCheck(d dialect, index, table, schema string) *goqu.SelectDataset {
return d.GOQU().Select(goqu.COUNT(goqu.Star()).Gt(0)).
From("information_schema.statistics").
Where(
@@ -97,19 +159,15 @@ func GenIndexCheck(d drivers.Dialect, index, table, schema string) *goqu.SelectD
)
}
func (t *CreateTableTemplate) String() string {
if t.TrColumnTypes == nil {
t.TrColumnTypes = ColumnTypeTranslator
}
func (t *CreateTable) String() string {
sql := "CREATE TABLE "
if !t.OmitIfNotExistsClause {
sql += "IF NOT EXISTS "
}
sql += "\"" + t.Name + "\" (\n"
sql += GenCreateTableBody(t.Table, t.TrColumnTypes)
sql += "\"" + t.Table.Name + "\" (\n"
sql += GenCreateTableBody(t.Table, t.Dialect.NativeColumnType)
sql += "\n)"
sql += t.SuffixClause
@@ -165,7 +223,7 @@ func GenPrimaryKey(pk *Index) string {
return sql
}
func (t *CreateIndexTemplate) String() string {
func (t *CreateIndex) String() string {
sql := "CREATE "
if t.Index.Unique {
@@ -212,6 +270,19 @@ func (t *CreateIndexTemplate) String() string {
return sql
}
func (c *AddColumn) String() string {
sql := "ALTER TABLE" + " " + c.Table.Name + " ADD COLUMN " + c.Column.Name + " " + c.Dialect.NativeColumnType(c.Column.Type)
if !c.Column.IsNull {
sql += " NOT NULL"
}
if len(c.Column.DefaultValue) > 0 {
sql += " DEFAULT " + c.Column.DefaultValue
}
return sql
}
func GetBool(ctx context.Context, db sqlx.QueryerContext, query exp.SQLExpression) (bool, error) {
var (
exists bool
+4
View File
@@ -2,6 +2,7 @@ package drivers
import (
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
)
@@ -30,5 +31,8 @@ type (
// TypeWrap returns driver's type implementation for a particular attribute type
TypeWrap(dal.Type) Type
// NativeColumnType converts column type to type that can be used in the underlying rdbms
NativeColumnType(columnType ddl.ColumnType) string
}
)
+15 -5
View File
@@ -47,7 +47,7 @@ func Connect(ctx context.Context, dsn string) (_ store.Storer, err error) {
TxRetryErrHandler: txRetryErrHandler,
ErrorHandler: errorHandler,
SchemaAPI: &schema{dbName: cfg.DBName, dialect: Dialect()},
SchemaAPI: &schema{dbName: cfg.DBName},
}
s.SetDefaults()
@@ -133,13 +133,23 @@ func connSetup(ctx context.Context, db sqlx.ExecerContext) (err error) {
}
func txRetryErrHandler(try int, err error) bool {
var (
mysqlErr *mysql.MySQLError
ok bool
)
// unwrap errors until we find one comming from MySQL
for errors.Unwrap(err) != nil {
err = errors.Unwrap(err)
}
var mysqlErr, ok = err.(*mysql.MySQLError)
if !ok || mysqlErr == nil {
return false
mysqlErr, ok = err.(*mysql.MySQLError)
if !ok || mysqlErr == nil {
return false
}
if mysqlErr != nil {
break
}
}
switch mysqlErr.Number {
+16 -10
View File
@@ -2,6 +2,7 @@ package mysql
import (
"fmt"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
"strconv"
"strings"
@@ -12,32 +13,33 @@ import (
)
type (
dialect struct{}
mysqlDialect struct{}
)
var (
goquDialectWrapper = goqu.Dialect("mysql")
_ drivers.Dialect = &mysqlDialect{}
_ drivers.Dialect = &dialect{}
dialect = &mysqlDialect{}
goquDialectWrapper = goqu.Dialect("mysql")
)
func Dialect() *dialect {
return &dialect{}
func Dialect() *mysqlDialect {
return dialect
}
func (dialect) GOQU() goqu.DialectWrapper {
func (mysqlDialect) GOQU() goqu.DialectWrapper {
return goquDialectWrapper
}
func (dialect) DeepIdentJSON(ident exp.IdentifierExpression, pp ...any) (exp.LiteralExpression, error) {
func (mysqlDialect) DeepIdentJSON(ident exp.IdentifierExpression, pp ...any) (exp.LiteralExpression, error) {
return JSONPath(ident, pp...)
}
func (d dialect) TableCodec(m *dal.Model) drivers.TableCodec {
func (d mysqlDialect) TableCodec(m *dal.Model) drivers.TableCodec {
return drivers.NewTableCodec(m, d)
}
func (d dialect) TypeWrap(dt dal.Type) drivers.Type {
func (d mysqlDialect) TypeWrap(dt dal.Type) drivers.Type {
// Any exception to general type-wrap implementation in the drivers package
// should be placed here
switch c := dt.(type) {
@@ -59,7 +61,7 @@ func (d dialect) TypeWrap(dt dal.Type) drivers.Type {
// AttributeCast for mySQL
//
// https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast
func (dialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (exp.LiteralExpression, error) {
func (mysqlDialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (exp.LiteralExpression, error) {
var (
c exp.CastExpression
)
@@ -96,6 +98,10 @@ func (dialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (ex
return exp.NewLiteralExpression("?", c), nil
}
func (mysqlDialect) NativeColumnType(ct ddl.ColumnType) string {
return columnTypeTranslator(ct)
}
func JSONPath(ident exp.IdentifierExpression, pp ...any) (exp.LiteralExpression, error) {
var (
sql strings.Builder
+47 -18
View File
@@ -9,23 +9,28 @@ import (
type (
schema struct {
dbName string
dialect *dialect
dbName string
}
)
// TableExists checks if table exists in the MySQL database
func (s *schema) TableExists(ctx context.Context, db sqlx.QueryerContext, table string) (bool, error) {
return ddl.TableExists(ctx, db, s.dialect, table, "public")
return ddl.TableExists(ctx, db, Dialect(), table, s.dbName)
}
// ColumnExists checks if column exists in the MySQL table
func (s *schema) ColumnExists(ctx context.Context, db sqlx.QueryerContext, column, table string) (bool, error) {
return ddl.ColumnExists(ctx, db, Dialect(), column, table, s.dbName)
}
// CreateTable
//
// MySQL does not hav CREATE-INDEX-IF-NOT-EXISTS; we need to check index existance manually
// MySQL does not hav CREATE-INDEX-IF-NOT-EXISTS; we need to check index existence manually
func (s *schema) CreateTable(ctx context.Context, db sqlx.ExtContext, t *ddl.Table) (err error) {
tc := &ddl.CreateTableTemplate{
Table: t,
TrColumnTypes: columnTypTranslator,
SuffixClause: "ENGINE=InnoDB DEFAULT CHARSET=utf8",
tc := &ddl.CreateTable{
Dialect: Dialect(),
Table: t,
SuffixClause: "ENGINE=InnoDB DEFAULT CHARSET=utf8",
}
if err = ddl.Exec(ctx, db, tc); err != nil {
@@ -42,13 +47,14 @@ func (s *schema) CreateTable(ctx context.Context, db sqlx.ExtContext, t *ddl.Tab
}
var doesIt bool
if doesIt, err = ddl.IndexExists(ctx, db, s.dialect, index.Name, index.Table, s.dbName); err != nil {
if doesIt, err = ddl.IndexExists(ctx, db, Dialect(), index.Name, index.Table, s.dbName); err != nil {
return
} else if doesIt {
continue
}
ic := &ddl.CreateIndexTemplate{
ic := &ddl.CreateIndex{
Dialect: Dialect(),
Index: index,
OmitIfNotExistsClause: true,
}
@@ -62,17 +68,40 @@ func (s *schema) CreateTable(ctx context.Context, db sqlx.ExtContext, t *ddl.Tab
return
}
func columnTypTranslator(ct ddl.ColumnType) string {
func (s *schema) AddColumn(ctx context.Context, db sqlx.ExtContext, t *ddl.Table, cc ...*ddl.Column) (err error) {
var (
aux []any
exists bool
)
for _, c := range cc {
// check column existence
if exists, err = s.ColumnExists(ctx, db, c.Name, t.Name); err != nil {
return
} else if exists {
// column exists
continue
}
// Sadly, some column types in MySQL can not have default values
if c.Type.Type == ddl.ColumnTypeJson || c.Type.Type == ddl.ColumnTypeBinary || c.Type.Type == ddl.ColumnTypeText {
c.DefaultValue = ""
}
aux = append(aux, &ddl.AddColumn{
Dialect: dialect,
Table: t,
Column: c,
})
}
return ddl.Exec(ctx, db, aux...)
}
func columnTypeTranslator(ct ddl.ColumnType) string {
switch ct.Type {
case ddl.ColumnTypeIdentifier:
return "BIGINT UNSIGNED"
case ddl.ColumnTypeText:
// @todo when compose_record_value is removed, this will no longer be needed
if y, has := ct.Flags["mysqlLongText"].(bool); has && y {
return "LONGTEXT"
}
return "TEXT"
case ddl.ColumnTypeBinary:
return "BLOB"
case ddl.ColumnTypeTimestamp:
@@ -2,38 +2,40 @@ package postgres
import (
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/drivers"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
)
type (
dialect struct{}
mysqlDialect struct{}
)
var (
goquDialectWrapper = goqu.Dialect("postgres")
_ drivers.Dialect = &mysqlDialect{}
_ drivers.Dialect = &dialect{}
dialect = &mysqlDialect{}
goquDialectWrapper = goqu.Dialect("postgres")
)
func Dialect() *dialect {
return &dialect{}
func Dialect() *mysqlDialect {
return dialect
}
func (dialect) GOQU() goqu.DialectWrapper {
func (mysqlDialect) GOQU() goqu.DialectWrapper {
return goquDialectWrapper
}
func (dialect) DeepIdentJSON(ident exp.IdentifierExpression, pp ...any) (exp.LiteralExpression, error) {
func (mysqlDialect) DeepIdentJSON(ident exp.IdentifierExpression, pp ...any) (exp.LiteralExpression, error) {
return drivers.DeepIdentJSON(ident, pp...), nil
}
func (d dialect) TableCodec(m *dal.Model) drivers.TableCodec {
func (d mysqlDialect) TableCodec(m *dal.Model) drivers.TableCodec {
return drivers.NewTableCodec(m, d)
}
func (d dialect) TypeWrap(dt dal.Type) drivers.Type {
func (d mysqlDialect) TypeWrap(dt dal.Type) drivers.Type {
// Any exception to general type-wrap implementation in the drivers package
// should be placed here
switch c := dt.(type) {
@@ -44,7 +46,7 @@ func (d dialect) TypeWrap(dt dal.Type) drivers.Type {
return drivers.TypeWrap(dt)
}
func (dialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (exp.LiteralExpression, error) {
func (mysqlDialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (exp.LiteralExpression, error) {
var (
c exp.CastExpression
)
@@ -69,3 +71,7 @@ func (dialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (ex
return exp.NewLiteralExpression("?", c), nil
}
func (mysqlDialect) NativeColumnType(ct ddl.ColumnType) string {
return ddl.ColumnTypeTranslator(ct)
}
@@ -13,22 +13,56 @@ import (
type (
schema struct {
schemaName string
dialect *dialect
}
)
var (
_ = &schema{}
)
func (s *schema) TableExists(ctx context.Context, db sqlx.QueryerContext, table string) (bool, error) {
return ddl.TableExists(ctx, db, s.dialect, table, "public")
return ddl.TableExists(ctx, db, Dialect(), table, "public")
}
// ColumnExists checks if column exists in the MySQL table
func (s *schema) ColumnExists(ctx context.Context, db sqlx.QueryerContext, column, table string) (bool, error) {
return ddl.ColumnExists(ctx, db, Dialect(), column, table, "public")
}
func (s *schema) CreateTable(ctx context.Context, db sqlx.ExtContext, t *ddl.Table) (err error) {
tt := append([]any{
&ddl.CreateTableTemplate{
&ddl.CreateTable{
Dialect: Dialect(),
Table: t,
SuffixClause: " WITHOUT OIDS",
}},
ddl.CreateIndexTemplates(&ddl.CreateIndexTemplate{OmitFieldLength: true}, t.Indexes...)...,
ddl.CreateIndexTemplates(&ddl.CreateIndex{OmitFieldLength: true}, t.Indexes...)...,
)
return ddl.Exec(ctx, db, tt...)
}
func (s *schema) AddColumn(ctx context.Context, db sqlx.ExtContext, t *ddl.Table, cc ...*ddl.Column) (err error) {
var (
aux []any
exists bool
)
for _, c := range cc {
// check column existence
if exists, err = s.ColumnExists(ctx, db, c.Name, t.Name); err != nil {
return
} else if exists {
// column exists
continue
}
aux = append(aux, &ddl.AddColumn{
Dialect: dialect,
Table: t,
Column: c,
})
}
return ddl.Exec(ctx, db, aux...)
}
@@ -73,7 +73,7 @@ func Connect(ctx context.Context, dsn string) (_ store.Storer, err error) {
TxRetryLimit: -1,
//TxRetryErrHandler: txRetryErrHandler,
SchemaAPI: &schema{dialect: Dialect()},
SchemaAPI: &schema{},
}
s.SetDefaults()
+16 -10
View File
@@ -2,6 +2,7 @@ package sqlite
import (
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/drivers"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/dialect/sqlite3"
@@ -9,13 +10,14 @@ import (
)
type (
dialect struct{}
sqliteDialect struct{}
)
var (
goquDialectWrapper = goqu.Dialect("sqlite3")
_ drivers.Dialect = &sqliteDialect{}
_ drivers.Dialect = &dialect{}
dialect = &sqliteDialect{}
goquDialectWrapper = goqu.Dialect("sqlite3")
)
func init() {
@@ -28,28 +30,32 @@ func init() {
goqu.RegisterDialect("sqlite3", d)
}
func Dialect() *dialect {
return &dialect{}
func Dialect() *sqliteDialect {
return dialect
}
func (dialect) GOQU() goqu.DialectWrapper {
func (sqliteDialect) GOQU() goqu.DialectWrapper {
return goquDialectWrapper
}
func (dialect) DeepIdentJSON(ident exp.IdentifierExpression, pp ...any) (exp.LiteralExpression, error) {
func (sqliteDialect) DeepIdentJSON(ident exp.IdentifierExpression, pp ...any) (exp.LiteralExpression, error) {
return drivers.DeepIdentJSON(ident, pp...), nil
}
func (d dialect) TableCodec(m *dal.Model) drivers.TableCodec {
func (d sqliteDialect) TableCodec(m *dal.Model) drivers.TableCodec {
return drivers.NewTableCodec(m, d)
}
func (d dialect) TypeWrap(t dal.Type) drivers.Type {
func (d sqliteDialect) TypeWrap(t dal.Type) drivers.Type {
// Any exception to general type-wrap implementation in the drivers package
// should be placed here
return drivers.TypeWrap(t)
}
func (dialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (exp.LiteralExpression, error) {
func (sqliteDialect) AttributeCast(attr *dal.Attribute, val exp.LiteralExpression) (exp.LiteralExpression, error) {
return drivers.AttributeCast(attr, val)
}
func (sqliteDialect) NativeColumnType(ct ddl.ColumnType) string {
return columnTypeTranslator(ct)
}
+44 -8
View File
@@ -9,9 +9,7 @@ import (
)
type (
schema struct {
dialect *dialect
}
schema struct{}
)
func (s *schema) TableExists(ctx context.Context, db sqlx.QueryerContext, table string) (bool, error) {
@@ -27,19 +25,57 @@ func (s *schema) TableExists(ctx context.Context, db sqlx.QueryerContext, table
return exists, nil
}
func (s *schema) ColumnExists(ctx context.Context, db sqlx.QueryerContext, column, table string) (bool, error) {
var (
exists bool
sql = `SELECT COUNT(*) > 0 FROM pragma_table_info(?) WHERE name = ?;`
)
if err := sqlx.GetContext(ctx, db, &exists, sql, table, column); err != nil {
return false, fmt.Errorf("could not check if column exists: %w", err)
}
return exists, nil
}
func (s *schema) CreateTable(ctx context.Context, db sqlx.ExtContext, t *ddl.Table) (err error) {
tt := append(
[]any{&ddl.CreateTableTemplate{
Table: t,
TrColumnTypes: columnTypTranslator,
[]any{&ddl.CreateTable{
Dialect: Dialect(),
Table: t,
}},
ddl.CreateIndexTemplates(&ddl.CreateIndexTemplate{OmitFieldLength: true}, t.Indexes...)...,
ddl.CreateIndexTemplates(&ddl.CreateIndex{OmitFieldLength: true}, t.Indexes...)...,
)
return ddl.Exec(ctx, db, tt...)
}
func columnTypTranslator(ct ddl.ColumnType) string {
func (s *schema) AddColumn(ctx context.Context, db sqlx.ExtContext, t *ddl.Table, cc ...*ddl.Column) (err error) {
var (
aux []any
exists bool
)
for _, c := range cc {
// check column existence
if exists, err = s.ColumnExists(ctx, db, c.Name, t.Name); err != nil {
return
} else if exists {
// column exists
continue
}
aux = append(aux, &ddl.AddColumn{
Dialect: dialect,
Table: t,
Column: c,
})
}
return ddl.Exec(ctx, db, aux...)
}
func columnTypeTranslator(ct ddl.ColumnType) string {
switch ct.Type {
case ddl.ColumnTypeTimestamp:
return "TIMESTAMP"
+1 -1
View File
@@ -12,7 +12,7 @@ import (
// It checks the given context for logger before falling back to one set on the store
func (s Store) log(ctx context.Context) *zap.Logger {
return logger.ContextValue(ctx, s.Logger, logger.Default(), zap.NewNop()).
Named("store.rdbms").
Named("store").
WithOptions(zap.AddCallerSkip(2))
}
+3 -2
View File
@@ -34,8 +34,9 @@ type (
}
schemaAPI interface {
TableExists(ctx context.Context, db sqlx.QueryerContext, table string) (bool, error)
CreateTable(ctx context.Context, db sqlx.ExtContext, t *ddl.Table) error
TableExists(context.Context, sqlx.QueryerContext, string) (bool, error)
CreateTable(context.Context, sqlx.ExtContext, *ddl.Table) error
AddColumn(context.Context, sqlx.ExtContext, *ddl.Table, ...*ddl.Column) error
}
Store struct {
+26 -31
View File
@@ -3,44 +3,39 @@ package rdbms
import (
"context"
"fmt"
"go.uber.org/zap"
)
func (s *Store) Upgrade(ctx context.Context) (err error) {
if err = UpgradeBeforeTableCreation(ctx, s); err != nil {
return
}
var (
tableExists bool
)
if err = UpgradeCreateTables(ctx, s); err != nil {
return
}
if err = UpgradeAfterTableCreation(ctx, s); err != nil {
return
}
return
}
// UpgradeBeforeTableCreation all actions that need to happen before tables are created
//
// Important note!
// Corteza needs to be updated to the latest patch release under 2022.3.x before upgrading to 2022.9!
func UpgradeBeforeTableCreation(ctx context.Context, s *Store) (err error) {
// @todo figure out how we will detect if database is on the latest version
return
}
// UpgradeCreateTables creates all tables needed by RDBMS store for Corteza to function properly
func UpgradeCreateTables(ctx context.Context, s *Store) (err error) {
for _, t := range Tables() {
if err = s.SchemaAPI.CreateTable(ctx, s.DB, t); err != nil {
return fmt.Errorf("could not create table %s: %w", t.Name, err)
tableExists, err = s.SchemaAPI.TableExists(ctx, s.DB, t.Name)
if err != nil {
return fmt.Errorf("could not check table %q existance: %w", t.Name, err)
}
if !tableExists {
s.log(ctx).Debug("creating table", zap.String("table", t.Name))
if err = s.SchemaAPI.CreateTable(ctx, s.DB, t); err != nil {
return fmt.Errorf("could not create table %q: %w", t.Name, err)
}
}
}
fixes := []func(context.Context, *Store) error{
fix202209_extendComposeModuleForPrivacyAndDAL,
fix202209_extendComposeModuleFieldsForPrivacyAndDAL,
}
for _, fix := range fixes {
if err = fix(ctx, s); err != nil {
return
}
}
return
}
func UpgradeAfterTableCreation(ctx context.Context, s *Store) (err error) {
return
}
+26
View File
@@ -0,0 +1,26 @@
package rdbms
import (
"context"
. "github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
)
func fix202209_extendComposeModuleForPrivacyAndDAL(ctx context.Context, s *Store) (err error) {
s.log(ctx).Info("extending compose_module table with privacy and model_config columns")
return s.SchemaAPI.AddColumn(
ctx, s.DB,
&Table{Name: "compose_module"},
&Column{Type: ColumnType{Type: ColumnTypeJson}, DefaultValue: "'{}'", Name: "privacy"},
&Column{Type: ColumnType{Type: ColumnTypeJson}, DefaultValue: "'{}'", Name: "model_config"},
)
}
func fix202209_extendComposeModuleFieldsForPrivacyAndDAL(ctx context.Context, s *Store) (err error) {
s.log(ctx).Info("extending compose_module_field table with privacy and encoding_strategy columns")
return s.SchemaAPI.AddColumn(
ctx, s.DB,
&Table{Name: "compose_module_field"},
&Column{Type: ColumnType{Type: ColumnTypeJson}, DefaultValue: "'{}'", Name: "privacy"},
&Column{Type: ColumnType{Type: ColumnTypeJson}, DefaultValue: "'{}'", Name: "encoding_strategy"},
)
}
-15
View File
@@ -59,7 +59,6 @@ func Tables() []*Table {
tableComposeNamespace(),
tableComposePage(),
tableComposeRecord(),
tableComposeRecordValue(),
tableFederationModuleShared(),
tableFederationModuleExposed(),
tableFederationModuleMapping(),
@@ -535,20 +534,6 @@ func tableComposeRecord() *Table {
)
}
func tableComposeRecordValue() *Table {
return TableDef("compose_record_value",
ColumnDef("record_id", ColumnTypeIdentifier),
ColumnDef("name", ColumnTypeVarchar, ColumnTypeLength(64)),
ColumnDef("value", ColumnTypeText, ColumnTypeFlag("mysqlLongText", true)),
ColumnDef("ref", ColumnTypeIdentifier),
ColumnDef("place", ColumnTypeInteger),
ColumnDef("deleted_at", ColumnTypeTimestamp, Null),
PrimaryKey(IColumn("record_id", "name", "place")),
AddIndex("ref", IColumn("ref"), IWhere("ref > 0")),
)
}
func tableFederationModuleShared() *Table {
return TableDef("federation_module_shared",
ID,