github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/store/sqlstore/supplier.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "context" 8 dbsql "database/sql" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "os" 13 "strings" 14 "sync/atomic" 15 "time" 16 17 sq "github.com/Masterminds/squirrel" 18 "github.com/dyatlov/go-opengraph/opengraph" 19 "github.com/go-sql-driver/mysql" 20 "github.com/lib/pq" 21 "github.com/mattermost/gorp" 22 "github.com/vnforks/kid/v5/einterfaces" 23 "github.com/vnforks/kid/v5/mlog" 24 "github.com/vnforks/kid/v5/model" 25 "github.com/vnforks/kid/v5/store" 26 "github.com/vnforks/kid/v5/utils" 27 ) 28 29 const ( 30 INDEX_TYPE_FULL_TEXT = "full_text" 31 INDEX_TYPE_DEFAULT = "default" 32 DB_PING_ATTEMPTS = 18 33 DB_PING_TIMEOUT_SECS = 10 34 ) 35 36 const ( 37 EXIT_GENERIC_FAILURE = 1 38 EXIT_CREATE_TABLE = 100 39 EXIT_DB_OPEN = 101 40 EXIT_PING = 102 41 EXIT_NO_DRIVER = 103 42 EXIT_TABLE_EXISTS = 104 43 EXIT_TABLE_EXISTS_MYSQL = 105 44 EXIT_COLUMN_EXISTS = 106 45 EXIT_DOES_COLUMN_EXISTS_POSTGRES = 107 46 EXIT_DOES_COLUMN_EXISTS_MYSQL = 108 47 EXIT_DOES_COLUMN_EXISTS_MISSING = 109 48 EXIT_CREATE_COLUMN_POSTGRES = 110 49 EXIT_CREATE_COLUMN_MYSQL = 111 50 EXIT_CREATE_COLUMN_MISSING = 112 51 EXIT_REMOVE_COLUMN = 113 52 EXIT_RENAME_COLUMN = 114 53 EXIT_MAX_COLUMN = 115 54 EXIT_ALTER_COLUMN = 116 55 EXIT_CREATE_INDEX_POSTGRES = 117 56 EXIT_CREATE_INDEX_MYSQL = 118 57 EXIT_CREATE_INDEX_FULL_MYSQL = 119 58 EXIT_CREATE_INDEX_MISSING = 120 59 EXIT_REMOVE_INDEX_POSTGRES = 121 60 EXIT_REMOVE_INDEX_MYSQL = 122 61 EXIT_REMOVE_INDEX_MISSING = 123 62 EXIT_REMOVE_TABLE = 134 63 EXIT_CREATE_INDEX_SQLITE = 135 64 EXIT_REMOVE_INDEX_SQLITE = 136 65 EXIT_TABLE_EXISTS_SQLITE = 137 66 EXIT_DOES_COLUMN_EXISTS_SQLITE = 138 67 EXIT_ALTER_PRIMARY_KEY = 139 68 ) 69 70 type SqlSupplierStores struct { 71 branch store.BranchStore 72 class store.ClassStore 73 post store.PostStore 74 user store.UserStore 75 audit store.AuditStore 76 cluster store.ClusterDiscoveryStore 77 compliance store.ComplianceStore 78 session store.SessionStore 79 oauth store.OAuthStore 80 system store.SystemStore 81 webhook store.WebhookStore 82 command store.CommandStore 83 commandWebhook store.CommandWebhookStore 84 preference store.PreferenceStore 85 license store.LicenseStore 86 token store.TokenStore 87 emoji store.EmojiStore 88 status store.StatusStore 89 fileInfo store.FileInfoStore 90 reaction store.ReactionStore 91 job store.JobStore 92 userAccessToken store.UserAccessTokenStore 93 role store.RoleStore 94 scheme store.SchemeStore 95 TermsOfService store.TermsOfServiceStore 96 UserTermsOfService store.UserTermsOfServiceStore 97 linkMetadata store.LinkMetadataStore 98 } 99 100 type SqlSupplier struct { 101 // rrCounter and srCounter should be kept first. 102 // See https://github.com/vnforks/kid/v5/pull/7281 103 rrCounter int64 104 srCounter int64 105 master *gorp.DbMap 106 replicas []*gorp.DbMap 107 searchReplicas []*gorp.DbMap 108 stores SqlSupplierStores 109 settings *model.SqlSettings 110 lockedToMaster bool 111 context context.Context 112 } 113 114 type TraceOnAdapter struct{} 115 116 func (t *TraceOnAdapter) Printf(format string, v ...interface{}) { 117 originalString := fmt.Sprintf(format, v...) 118 newString := strings.ReplaceAll(originalString, "\n", " ") 119 newString = strings.ReplaceAll(newString, "\t", " ") 120 newString = strings.ReplaceAll(newString, "\"", "") 121 mlog.Debug(newString) 122 } 123 124 func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlSupplier { 125 supplier := &SqlSupplier{ 126 rrCounter: 0, 127 srCounter: 0, 128 settings: &settings, 129 } 130 131 supplier.initConnection() 132 133 supplier.stores.branch = newSqlBranchStore(supplier) 134 supplier.stores.class = newSqlClassStore(supplier, metrics) 135 supplier.stores.post = newSqlPostStore(supplier, metrics) 136 supplier.stores.user = newSqlUserStore(supplier, metrics) 137 supplier.stores.audit = newSqlAuditStore(supplier) 138 supplier.stores.cluster = newSqlClusterDiscoveryStore(supplier) 139 supplier.stores.compliance = newSqlComplianceStore(supplier) 140 supplier.stores.session = newSqlSessionStore(supplier) 141 supplier.stores.oauth = newSqlOAuthStore(supplier) 142 supplier.stores.system = newSqlSystemStore(supplier) 143 supplier.stores.webhook = newSqlWebhookStore(supplier, metrics) 144 supplier.stores.command = newSqlCommandStore(supplier) 145 supplier.stores.commandWebhook = newSqlCommandWebhookStore(supplier) 146 supplier.stores.preference = newSqlPreferenceStore(supplier) 147 supplier.stores.license = newSqlLicenseStore(supplier) 148 supplier.stores.token = newSqlTokenStore(supplier) 149 supplier.stores.emoji = newSqlEmojiStore(supplier, metrics) 150 supplier.stores.status = newSqlStatusStore(supplier) 151 supplier.stores.fileInfo = newSqlFileInfoStore(supplier, metrics) 152 supplier.stores.job = newSqlJobStore(supplier) 153 supplier.stores.userAccessToken = newSqlUserAccessTokenStore(supplier) 154 supplier.stores.TermsOfService = newSqlTermsOfServiceStore(supplier, metrics) 155 supplier.stores.UserTermsOfService = newSqlUserTermsOfServiceStore(supplier) 156 supplier.stores.linkMetadata = newSqlLinkMetadataStore(supplier) 157 supplier.stores.reaction = newSqlReactionStore(supplier) 158 supplier.stores.role = newSqlRoleStore(supplier) 159 supplier.stores.scheme = newSqlSchemeStore(supplier) 160 161 err := supplier.GetMaster().CreateTablesIfNotExists() 162 if err != nil { 163 mlog.Critical("Error creating database tables.", mlog.Err(err)) 164 time.Sleep(time.Second) 165 os.Exit(EXIT_CREATE_TABLE) 166 } 167 168 // err = upgradeDatabase(supplier, model.CurrentVersion) 169 // if err != nil { 170 // mlog.Critical("Failed to upgrade database.", mlog.Err(err)) 171 // time.Sleep(time.Second) 172 // os.Exit(EXIT_GENERIC_FAILURE) 173 // } 174 175 supplier.stores.branch.(*SqlBranchStore).createIndexesIfNotExists() 176 supplier.stores.class.(*SqlClassStore).createIndexesIfNotExists() 177 supplier.stores.post.(*SqlPostStore).createIndexesIfNotExists() 178 supplier.stores.user.(*SqlUserStore).createIndexesIfNotExists() 179 supplier.stores.audit.(*SqlAuditStore).createIndexesIfNotExists() 180 supplier.stores.compliance.(*SqlComplianceStore).createIndexesIfNotExists() 181 supplier.stores.session.(*SqlSessionStore).createIndexesIfNotExists() 182 supplier.stores.oauth.(*SqlOAuthStore).createIndexesIfNotExists() 183 supplier.stores.system.(*SqlSystemStore).createIndexesIfNotExists() 184 supplier.stores.webhook.(*SqlWebhookStore).createIndexesIfNotExists() 185 supplier.stores.command.(*SqlCommandStore).createIndexesIfNotExists() 186 supplier.stores.commandWebhook.(*SqlCommandWebhookStore).createIndexesIfNotExists() 187 supplier.stores.preference.(*SqlPreferenceStore).createIndexesIfNotExists() 188 supplier.stores.license.(*SqlLicenseStore).createIndexesIfNotExists() 189 supplier.stores.token.(*SqlTokenStore).createIndexesIfNotExists() 190 supplier.stores.emoji.(*SqlEmojiStore).createIndexesIfNotExists() 191 supplier.stores.status.(*SqlStatusStore).createIndexesIfNotExists() 192 supplier.stores.fileInfo.(*SqlFileInfoStore).createIndexesIfNotExists() 193 supplier.stores.job.(*SqlJobStore).createIndexesIfNotExists() 194 supplier.stores.userAccessToken.(*SqlUserAccessTokenStore).createIndexesIfNotExists() 195 supplier.stores.TermsOfService.(SqlTermsOfServiceStore).createIndexesIfNotExists() 196 supplier.stores.UserTermsOfService.(SqlUserTermsOfServiceStore).createIndexesIfNotExists() 197 supplier.stores.linkMetadata.(*SqlLinkMetadataStore).createIndexesIfNotExists() 198 supplier.stores.scheme.(*SqlSchemeStore).createIndexesIfNotExists() 199 supplier.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures() 200 201 return supplier 202 } 203 204 func setupConnection(con_type string, dataSource string, settings *model.SqlSettings) *gorp.DbMap { 205 db, err := dbsql.Open(*settings.DriverName, dataSource) 206 if err != nil { 207 mlog.Critical("Failed to open SQL connection to err.", mlog.Err(err)) 208 time.Sleep(time.Second) 209 os.Exit(EXIT_DB_OPEN) 210 } 211 212 for i := 0; i < DB_PING_ATTEMPTS; i++ { 213 mlog.Info("Pinging SQL", mlog.String("database", con_type)) 214 ctx, cancel := context.WithTimeout(context.Background(), DB_PING_TIMEOUT_SECS*time.Second) 215 defer cancel() 216 err = db.PingContext(ctx) 217 if err == nil { 218 break 219 } else { 220 if i == DB_PING_ATTEMPTS-1 { 221 mlog.Critical("Failed to ping DB, server will exit.", mlog.Err(err)) 222 time.Sleep(time.Second) 223 os.Exit(EXIT_PING) 224 } else { 225 mlog.Error("Failed to ping DB", mlog.Err(err), mlog.Int("retrying in seconds", DB_PING_TIMEOUT_SECS)) 226 time.Sleep(DB_PING_TIMEOUT_SECS * time.Second) 227 } 228 } 229 } 230 231 db.SetMaxIdleConns(*settings.MaxIdleConns) 232 db.SetMaxOpenConns(*settings.MaxOpenConns) 233 db.SetConnMaxLifetime(time.Duration(*settings.ConnMaxLifetimeMilliseconds) * time.Millisecond) 234 235 var dbmap *gorp.DbMap 236 237 connectionTimeout := time.Duration(*settings.QueryTimeout) * time.Second 238 239 if *settings.DriverName == model.DATABASE_DRIVER_SQLITE { 240 dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}, QueryTimeout: connectionTimeout} 241 } else if *settings.DriverName == model.DATABASE_DRIVER_MYSQL { 242 dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"}, QueryTimeout: connectionTimeout} 243 } else if *settings.DriverName == model.DATABASE_DRIVER_POSTGRES { 244 dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}, QueryTimeout: connectionTimeout} 245 } else { 246 mlog.Critical("Failed to create dialect specific driver") 247 time.Sleep(time.Second) 248 os.Exit(EXIT_NO_DRIVER) 249 } 250 251 if settings.Trace != nil && *settings.Trace { 252 dbmap.TraceOn("sql-trace:", &TraceOnAdapter{}) 253 } 254 255 return dbmap 256 } 257 258 func (ss *SqlSupplier) SetContext(context context.Context) { 259 ss.context = context 260 } 261 262 func (ss *SqlSupplier) Context() context.Context { 263 return ss.context 264 } 265 266 func (ss *SqlSupplier) initConnection() { 267 ss.master = setupConnection("master", *ss.settings.DataSource, ss.settings) 268 269 if len(ss.settings.DataSourceReplicas) > 0 { 270 ss.replicas = make([]*gorp.DbMap, len(ss.settings.DataSourceReplicas)) 271 for i, replica := range ss.settings.DataSourceReplicas { 272 ss.replicas[i] = setupConnection(fmt.Sprintf("replica-%v", i), replica, ss.settings) 273 } 274 } 275 276 if len(ss.settings.DataSourceSearchReplicas) > 0 { 277 ss.searchReplicas = make([]*gorp.DbMap, len(ss.settings.DataSourceSearchReplicas)) 278 for i, replica := range ss.settings.DataSourceSearchReplicas { 279 ss.searchReplicas[i] = setupConnection(fmt.Sprintf("search-replica-%v", i), replica, ss.settings) 280 } 281 } 282 } 283 284 func (ss *SqlSupplier) DriverName() string { 285 return *ss.settings.DriverName 286 } 287 288 func (ss *SqlSupplier) GetCurrentSchemaVersion() string { 289 version, _ := ss.GetMaster().SelectStr("SELECT Value FROM Systems WHERE Name='Version'") 290 return version 291 } 292 293 func (ss *SqlSupplier) GetMaster() *gorp.DbMap { 294 return ss.master 295 } 296 297 func (ss *SqlSupplier) GetSearchReplica() *gorp.DbMap { 298 if len(ss.settings.DataSourceSearchReplicas) == 0 { 299 return ss.GetReplica() 300 } 301 302 rrNum := atomic.AddInt64(&ss.srCounter, 1) % int64(len(ss.searchReplicas)) 303 return ss.searchReplicas[rrNum] 304 } 305 306 func (ss *SqlSupplier) GetReplica() *gorp.DbMap { 307 if len(ss.settings.DataSourceReplicas) == 0 || ss.lockedToMaster { 308 return ss.GetMaster() 309 } 310 311 rrNum := atomic.AddInt64(&ss.rrCounter, 1) % int64(len(ss.replicas)) 312 return ss.replicas[rrNum] 313 } 314 315 func (ss *SqlSupplier) TotalMasterDbConnections() int { 316 return ss.GetMaster().Db.Stats().OpenConnections 317 } 318 319 func (ss *SqlSupplier) TotalReadDbConnections() int { 320 if len(ss.settings.DataSourceReplicas) == 0 { 321 return 0 322 } 323 324 count := 0 325 for _, db := range ss.replicas { 326 count = count + db.Db.Stats().OpenConnections 327 } 328 329 return count 330 } 331 332 func (ss *SqlSupplier) TotalSearchDbConnections() int { 333 if len(ss.settings.DataSourceSearchReplicas) == 0 { 334 return 0 335 } 336 337 count := 0 338 for _, db := range ss.searchReplicas { 339 count = count + db.Db.Stats().OpenConnections 340 } 341 342 return count 343 } 344 345 func (ss *SqlSupplier) MarkSystemRanUnitTests() { 346 props, err := ss.System().Get() 347 if err != nil { 348 return 349 } 350 351 unitTests := props[model.SYSTEM_RAN_UNIT_TESTS] 352 if len(unitTests) == 0 { 353 systemTests := &model.System{Name: model.SYSTEM_RAN_UNIT_TESTS, Value: "1"} 354 ss.System().Save(systemTests) 355 } 356 } 357 358 func (ss *SqlSupplier) DoesTableExist(tableName string) bool { 359 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 360 count, err := ss.GetMaster().SelectInt( 361 `SELECT count(relname) FROM pg_class WHERE relname=$1`, 362 strings.ToLower(tableName), 363 ) 364 365 if err != nil { 366 mlog.Critical("Failed to check if table exists", mlog.Err(err)) 367 time.Sleep(time.Second) 368 os.Exit(EXIT_TABLE_EXISTS) 369 } 370 371 return count > 0 372 373 } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 374 375 count, err := ss.GetMaster().SelectInt( 376 `SELECT 377 COUNT(0) AS table_exists 378 FROM 379 information_schema.TABLES 380 WHERE 381 TABLE_SCHEMA = DATABASE() 382 AND TABLE_NAME = ? 383 `, 384 tableName, 385 ) 386 387 if err != nil { 388 mlog.Critical("Failed to check if table exists", mlog.Err(err)) 389 time.Sleep(time.Second) 390 os.Exit(EXIT_TABLE_EXISTS_MYSQL) 391 } 392 393 return count > 0 394 395 } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { 396 count, err := ss.GetMaster().SelectInt( 397 `SELECT name FROM sqlite_master WHERE type='table' AND name=?`, 398 tableName, 399 ) 400 401 if err != nil { 402 mlog.Critical("Failed to check if table exists", mlog.Err(err)) 403 time.Sleep(time.Second) 404 os.Exit(EXIT_TABLE_EXISTS_SQLITE) 405 } 406 407 return count > 0 408 409 } else { 410 mlog.Critical("Failed to check if column exists because of missing driver") 411 time.Sleep(time.Second) 412 os.Exit(EXIT_COLUMN_EXISTS) 413 return false 414 } 415 } 416 417 func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool { 418 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 419 count, err := ss.GetMaster().SelectInt( 420 `SELECT COUNT(0) 421 FROM pg_attribute 422 WHERE attrelid = $1::regclass 423 AND attname = $2 424 AND NOT attisdropped`, 425 strings.ToLower(tableName), 426 strings.ToLower(columnName), 427 ) 428 429 if err != nil { 430 if err.Error() == "pq: relation \""+strings.ToLower(tableName)+"\" does not exist" { 431 return false 432 } 433 434 mlog.Critical("Failed to check if column exists", mlog.Err(err)) 435 time.Sleep(time.Second) 436 os.Exit(EXIT_DOES_COLUMN_EXISTS_POSTGRES) 437 } 438 439 return count > 0 440 441 } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 442 443 count, err := ss.GetMaster().SelectInt( 444 `SELECT 445 COUNT(0) AS column_exists 446 FROM 447 information_schema.COLUMNS 448 WHERE 449 TABLE_SCHEMA = DATABASE() 450 AND TABLE_NAME = ? 451 AND COLUMN_NAME = ?`, 452 tableName, 453 columnName, 454 ) 455 456 if err != nil { 457 mlog.Critical("Failed to check if column exists", mlog.Err(err)) 458 time.Sleep(time.Second) 459 os.Exit(EXIT_DOES_COLUMN_EXISTS_MYSQL) 460 } 461 462 return count > 0 463 464 } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { 465 count, err := ss.GetMaster().SelectInt( 466 `SELECT COUNT(*) FROM pragma_table_info(?) WHERE name=?`, 467 tableName, 468 columnName, 469 ) 470 471 if err != nil { 472 mlog.Critical("Failed to check if column exists", mlog.Err(err)) 473 time.Sleep(time.Second) 474 os.Exit(EXIT_DOES_COLUMN_EXISTS_SQLITE) 475 } 476 477 return count > 0 478 479 } else { 480 mlog.Critical("Failed to check if column exists because of missing driver") 481 time.Sleep(time.Second) 482 os.Exit(EXIT_DOES_COLUMN_EXISTS_MISSING) 483 return false 484 } 485 } 486 487 func (ss *SqlSupplier) DoesTriggerExist(triggerName string) bool { 488 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 489 count, err := ss.GetMaster().SelectInt(` 490 SELECT 491 COUNT(0) 492 FROM 493 pg_trigger 494 WHERE 495 tgname = $1 496 `, triggerName) 497 498 if err != nil { 499 mlog.Critical("Failed to check if trigger exists", mlog.Err(err)) 500 time.Sleep(time.Second) 501 os.Exit(EXIT_GENERIC_FAILURE) 502 } 503 504 return count > 0 505 506 } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 507 count, err := ss.GetMaster().SelectInt(` 508 SELECT 509 COUNT(0) 510 FROM 511 information_schema.triggers 512 WHERE 513 trigger_schema = DATABASE() 514 AND trigger_name = ? 515 `, triggerName) 516 517 if err != nil { 518 mlog.Critical("Failed to check if trigger exists", mlog.Err(err)) 519 time.Sleep(time.Second) 520 os.Exit(EXIT_GENERIC_FAILURE) 521 } 522 523 return count > 0 524 525 } else { 526 mlog.Critical("Failed to check if column exists because of missing driver") 527 time.Sleep(time.Second) 528 os.Exit(EXIT_GENERIC_FAILURE) 529 return false 530 } 531 } 532 533 func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName string, mySqlColType string, postgresColType string, defaultValue string) bool { 534 535 if ss.DoesColumnExist(tableName, columnName) { 536 return false 537 } 538 539 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 540 _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'") 541 if err != nil { 542 mlog.Critical("Failed to create column", mlog.Err(err)) 543 time.Sleep(time.Second) 544 os.Exit(EXIT_CREATE_COLUMN_POSTGRES) 545 } 546 547 return true 548 549 } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 550 _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'") 551 if err != nil { 552 mlog.Critical("Failed to create column", mlog.Err(err)) 553 time.Sleep(time.Second) 554 os.Exit(EXIT_CREATE_COLUMN_MYSQL) 555 } 556 557 return true 558 559 } else { 560 mlog.Critical("Failed to create column because of missing driver") 561 time.Sleep(time.Second) 562 os.Exit(EXIT_CREATE_COLUMN_MISSING) 563 return false 564 } 565 } 566 567 func (ss *SqlSupplier) CreateColumnIfNotExistsNoDefault(tableName string, columnName string, mySqlColType string, postgresColType string) bool { 568 569 if ss.DoesColumnExist(tableName, columnName) { 570 return false 571 } 572 573 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 574 _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType) 575 if err != nil { 576 mlog.Critical("Failed to create column", mlog.Err(err)) 577 time.Sleep(time.Second) 578 os.Exit(EXIT_CREATE_COLUMN_POSTGRES) 579 } 580 581 return true 582 583 } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 584 _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType) 585 if err != nil { 586 mlog.Critical("Failed to create column", mlog.Err(err)) 587 time.Sleep(time.Second) 588 os.Exit(EXIT_CREATE_COLUMN_MYSQL) 589 } 590 591 return true 592 593 } else { 594 mlog.Critical("Failed to create column because of missing driver") 595 time.Sleep(time.Second) 596 os.Exit(EXIT_CREATE_COLUMN_MISSING) 597 return false 598 } 599 } 600 601 func (ss *SqlSupplier) RemoveColumnIfExists(tableName string, columnName string) bool { 602 603 if !ss.DoesColumnExist(tableName, columnName) { 604 return false 605 } 606 607 _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " DROP COLUMN " + columnName) 608 if err != nil { 609 mlog.Critical("Failed to drop column", mlog.Err(err)) 610 time.Sleep(time.Second) 611 os.Exit(EXIT_REMOVE_COLUMN) 612 } 613 614 return true 615 } 616 617 func (ss *SqlSupplier) RemoveTableIfExists(tableName string) bool { 618 if !ss.DoesTableExist(tableName) { 619 return false 620 } 621 622 _, err := ss.GetMaster().ExecNoTimeout("DROP TABLE " + tableName) 623 if err != nil { 624 mlog.Critical("Failed to drop table", mlog.Err(err)) 625 time.Sleep(time.Second) 626 os.Exit(EXIT_REMOVE_TABLE) 627 } 628 629 return true 630 } 631 632 func (ss *SqlSupplier) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool { 633 if !ss.DoesColumnExist(tableName, oldColumnName) { 634 return false 635 } 636 637 var err error 638 if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 639 _, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " CHANGE " + oldColumnName + " " + newColumnName + " " + colType) 640 } else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 641 _, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " RENAME COLUMN " + oldColumnName + " TO " + newColumnName) 642 } 643 644 if err != nil { 645 mlog.Critical("Failed to rename column", mlog.Err(err)) 646 time.Sleep(time.Second) 647 os.Exit(EXIT_RENAME_COLUMN) 648 } 649 650 return true 651 } 652 653 func (ss *SqlSupplier) GetMaxLengthOfColumnIfExists(tableName string, columnName string) string { 654 if !ss.DoesColumnExist(tableName, columnName) { 655 return "" 656 } 657 658 var result string 659 var err error 660 if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 661 result, err = ss.GetMaster().SelectStr("SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.columns WHERE table_name = '" + tableName + "' AND COLUMN_NAME = '" + columnName + "'") 662 } else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 663 result, err = ss.GetMaster().SelectStr("SELECT character_maximum_length FROM information_schema.columns WHERE table_name = '" + strings.ToLower(tableName) + "' AND column_name = '" + strings.ToLower(columnName) + "'") 664 } 665 666 if err != nil { 667 mlog.Critical("Failed to get max length of column", mlog.Err(err)) 668 time.Sleep(time.Second) 669 os.Exit(EXIT_MAX_COLUMN) 670 } 671 672 return result 673 } 674 675 func (ss *SqlSupplier) AlterColumnTypeIfExists(tableName string, columnName string, mySqlColType string, postgresColType string) bool { 676 if !ss.DoesColumnExist(tableName, columnName) { 677 return false 678 } 679 680 var err error 681 if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 682 _, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " MODIFY " + columnName + " " + mySqlColType) 683 } else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 684 _, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + strings.ToLower(tableName) + " ALTER COLUMN " + strings.ToLower(columnName) + " TYPE " + postgresColType) 685 } 686 687 if err != nil { 688 mlog.Critical("Failed to alter column type", mlog.Err(err)) 689 time.Sleep(time.Second) 690 os.Exit(EXIT_ALTER_COLUMN) 691 } 692 693 return true 694 } 695 696 func (ss *SqlSupplier) AlterColumnDefaultIfExists(tableName string, columnName string, mySqlColDefault *string, postgresColDefault *string) bool { 697 if !ss.DoesColumnExist(tableName, columnName) { 698 return false 699 } 700 701 var defaultValue string 702 if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 703 // Some column types in MySQL cannot have defaults, so don't try to configure anything. 704 if mySqlColDefault == nil { 705 return true 706 } 707 708 defaultValue = *mySqlColDefault 709 } else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 710 // Postgres doesn't have the same limitation, but preserve the interface. 711 if postgresColDefault == nil { 712 return true 713 } 714 715 tableName = strings.ToLower(tableName) 716 columnName = strings.ToLower(columnName) 717 defaultValue = *postgresColDefault 718 } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { 719 // SQLite doesn't support altering column defaults, but we don't use this in 720 // production so just ignore. 721 return true 722 } else { 723 mlog.Critical("Failed to alter column default because of missing driver") 724 time.Sleep(time.Second) 725 os.Exit(EXIT_GENERIC_FAILURE) 726 return false 727 } 728 729 var err error 730 if defaultValue == "" { 731 _, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ALTER COLUMN " + columnName + " DROP DEFAULT") 732 } else { 733 _, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ALTER COLUMN " + columnName + " SET DEFAULT " + defaultValue) 734 } 735 736 if err != nil { 737 mlog.Critical("Failed to alter column", mlog.String("table", tableName), mlog.String("column", columnName), mlog.String("default value", defaultValue), mlog.Err(err)) 738 time.Sleep(time.Second) 739 os.Exit(EXIT_GENERIC_FAILURE) 740 return false 741 } 742 743 return true 744 } 745 746 func (ss *SqlSupplier) AlterPrimaryKey(tableName string, columnNames []string) bool { 747 var currentPrimaryKey string 748 var err error 749 // get the current primary key as a comma separated list of columns 750 if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 751 query := ` 752 SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index) AS PK 753 FROM 754 information_schema.statistics 755 WHERE 756 table_schema = DATABASE() 757 AND table_name = ? 758 AND index_name = 'PRIMARY' 759 GROUP BY 760 index_name` 761 currentPrimaryKey, err = ss.GetMaster().SelectStr(query, tableName) 762 } else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 763 query := ` 764 SELECT string_agg(a.attname, ',') AS pk 765 FROM 766 pg_constraint AS c 767 CROSS JOIN LATERAL 768 UNNEST(c.conkey) AS cols(colnum) 769 INNER JOIN 770 pg_attribute AS a ON a.attrelid = c.conrelid 771 AND cols.colnum = a.attnum 772 WHERE 773 c.contype = 'p' 774 AND c.conrelid = '` + strings.ToLower(tableName) + `'::REGCLASS` 775 currentPrimaryKey, err = ss.GetMaster().SelectStr(query) 776 } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { 777 // SQLite doesn't support altering primary key 778 return true 779 } 780 if err != nil { 781 mlog.Critical("Failed to get current primary key", mlog.String("table", tableName), mlog.Err(err)) 782 time.Sleep(time.Second) 783 os.Exit(EXIT_ALTER_PRIMARY_KEY) 784 } 785 786 primaryKey := strings.Join(columnNames, ",") 787 if strings.EqualFold(currentPrimaryKey, primaryKey) { 788 return false 789 } 790 // alter primary key 791 var alterQuery string 792 if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 793 alterQuery = "ALTER TABLE " + tableName + " DROP PRIMARY KEY, ADD PRIMARY KEY (" + primaryKey + ")" 794 } else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 795 alterQuery = "ALTER TABLE " + tableName + " DROP CONSTRAINT " + strings.ToLower(tableName) + "_pkey, ADD PRIMARY KEY (" + strings.ToLower(primaryKey) + ")" 796 } 797 _, err = ss.GetMaster().ExecNoTimeout(alterQuery) 798 if err != nil { 799 mlog.Critical("Failed to alter primary key", mlog.String("table", tableName), mlog.Err(err)) 800 time.Sleep(time.Second) 801 os.Exit(EXIT_ALTER_PRIMARY_KEY) 802 } 803 return true 804 } 805 806 func (ss *SqlSupplier) CreateUniqueIndexIfNotExists(indexName string, tableName string, columnName string) bool { 807 return ss.createIndexIfNotExists(indexName, tableName, []string{columnName}, INDEX_TYPE_DEFAULT, true) 808 } 809 810 func (ss *SqlSupplier) CreateIndexIfNotExists(indexName string, tableName string, columnName string) bool { 811 return ss.createIndexIfNotExists(indexName, tableName, []string{columnName}, INDEX_TYPE_DEFAULT, false) 812 } 813 814 func (ss *SqlSupplier) CreateCompositeIndexIfNotExists(indexName string, tableName string, columnNames []string) bool { 815 return ss.createIndexIfNotExists(indexName, tableName, columnNames, INDEX_TYPE_DEFAULT, false) 816 } 817 818 func (ss *SqlSupplier) CreateFullTextIndexIfNotExists(indexName string, tableName string, columnName string) bool { 819 return ss.createIndexIfNotExists(indexName, tableName, []string{columnName}, INDEX_TYPE_FULL_TEXT, false) 820 } 821 822 func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string, columnNames []string, indexType string, unique bool) bool { 823 824 uniqueStr := "" 825 if unique { 826 uniqueStr = "UNIQUE " 827 } 828 829 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 830 _, errExists := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName) 831 // It should fail if the index does not exist 832 if errExists == nil { 833 return false 834 } 835 836 query := "" 837 if indexType == INDEX_TYPE_FULL_TEXT { 838 if len(columnNames) != 1 { 839 mlog.Critical("Unable to create multi column full text index") 840 os.Exit(EXIT_CREATE_INDEX_POSTGRES) 841 } 842 columnName := columnNames[0] 843 postgresColumnNames := convertMySQLFullTextColumnsToPostgres(columnName) 844 query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + postgresColumnNames + "))" 845 } else { 846 query = "CREATE " + uniqueStr + "INDEX " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")" 847 } 848 849 _, err := ss.GetMaster().ExecNoTimeout(query) 850 if err != nil { 851 mlog.Critical("Failed to create index", mlog.Err(errExists), mlog.Err(err)) 852 time.Sleep(time.Second) 853 os.Exit(EXIT_CREATE_INDEX_POSTGRES) 854 } 855 } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 856 857 count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) 858 if err != nil { 859 mlog.Critical("Failed to check index", mlog.Err(err)) 860 time.Sleep(time.Second) 861 os.Exit(EXIT_CREATE_INDEX_MYSQL) 862 } 863 864 if count > 0 { 865 return false 866 } 867 868 fullTextIndex := "" 869 if indexType == INDEX_TYPE_FULL_TEXT { 870 fullTextIndex = " FULLTEXT " 871 } 872 873 _, err = ss.GetMaster().ExecNoTimeout("CREATE " + uniqueStr + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")") 874 if err != nil { 875 mlog.Critical("Failed to create index", mlog.Err(err)) 876 time.Sleep(time.Second) 877 os.Exit(EXIT_CREATE_INDEX_FULL_MYSQL) 878 } 879 } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { 880 _, err := ss.GetMaster().ExecNoTimeout("CREATE INDEX IF NOT EXISTS " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")") 881 if err != nil { 882 mlog.Critical("Failed to create index", mlog.Err(err)) 883 time.Sleep(time.Second) 884 os.Exit(EXIT_CREATE_INDEX_SQLITE) 885 } 886 } else { 887 mlog.Critical("Failed to create index because of missing driver") 888 time.Sleep(time.Second) 889 os.Exit(EXIT_CREATE_INDEX_MISSING) 890 } 891 892 return true 893 } 894 895 func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) bool { 896 897 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 898 _, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName) 899 // It should fail if the index does not exist 900 if err != nil { 901 return false 902 } 903 904 _, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName) 905 if err != nil { 906 mlog.Critical("Failed to remove index", mlog.Err(err)) 907 time.Sleep(time.Second) 908 os.Exit(EXIT_REMOVE_INDEX_POSTGRES) 909 } 910 911 return true 912 } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { 913 914 count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) 915 if err != nil { 916 mlog.Critical("Failed to check index", mlog.Err(err)) 917 time.Sleep(time.Second) 918 os.Exit(EXIT_REMOVE_INDEX_MYSQL) 919 } 920 921 if count <= 0 { 922 return false 923 } 924 925 _, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName + " ON " + tableName) 926 if err != nil { 927 mlog.Critical("Failed to remove index", mlog.Err(err)) 928 time.Sleep(time.Second) 929 os.Exit(EXIT_REMOVE_INDEX_MYSQL) 930 } 931 } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { 932 _, err := ss.GetMaster().ExecNoTimeout("DROP INDEX IF EXISTS " + indexName) 933 if err != nil { 934 mlog.Critical("Failed to remove index", mlog.Err(err)) 935 time.Sleep(time.Second) 936 os.Exit(EXIT_REMOVE_INDEX_SQLITE) 937 } 938 } else { 939 mlog.Critical("Failed to create index because of missing driver") 940 time.Sleep(time.Second) 941 os.Exit(EXIT_REMOVE_INDEX_MISSING) 942 } 943 944 return true 945 } 946 947 func IsUniqueConstraintError(err error, indexName []string) bool { 948 unique := false 949 if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "23505" { 950 unique = true 951 } 952 953 if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == 1062 { 954 unique = true 955 } 956 957 field := false 958 for _, contain := range indexName { 959 if strings.Contains(err.Error(), contain) { 960 field = true 961 break 962 } 963 } 964 965 return unique && field 966 } 967 968 func (ss *SqlSupplier) GetAllConns() []*gorp.DbMap { 969 all := make([]*gorp.DbMap, len(ss.replicas)+1) 970 copy(all, ss.replicas) 971 all[len(ss.replicas)] = ss.master 972 return all 973 } 974 975 func (ss *SqlSupplier) Close() { 976 ss.master.Db.Close() 977 for _, replica := range ss.replicas { 978 replica.Db.Close() 979 } 980 } 981 982 func (ss *SqlSupplier) LockToMaster() { 983 ss.lockedToMaster = true 984 } 985 986 func (ss *SqlSupplier) UnlockFromMaster() { 987 ss.lockedToMaster = false 988 } 989 990 func (ss *SqlSupplier) Branch() store.BranchStore { 991 return ss.stores.branch 992 } 993 994 func (ss *SqlSupplier) Class() store.ClassStore { 995 return ss.stores.class 996 } 997 998 func (ss *SqlSupplier) Post() store.PostStore { 999 return ss.stores.post 1000 } 1001 1002 func (ss *SqlSupplier) User() store.UserStore { 1003 return ss.stores.user 1004 } 1005 1006 func (ss *SqlSupplier) Session() store.SessionStore { 1007 return ss.stores.session 1008 } 1009 1010 func (ss *SqlSupplier) Audit() store.AuditStore { 1011 return ss.stores.audit 1012 } 1013 1014 func (ss *SqlSupplier) ClusterDiscovery() store.ClusterDiscoveryStore { 1015 return ss.stores.cluster 1016 } 1017 1018 func (ss *SqlSupplier) Compliance() store.ComplianceStore { 1019 return ss.stores.compliance 1020 } 1021 1022 func (ss *SqlSupplier) OAuth() store.OAuthStore { 1023 return ss.stores.oauth 1024 } 1025 1026 func (ss *SqlSupplier) System() store.SystemStore { 1027 return ss.stores.system 1028 } 1029 1030 func (ss *SqlSupplier) Webhook() store.WebhookStore { 1031 return ss.stores.webhook 1032 } 1033 1034 func (ss *SqlSupplier) Command() store.CommandStore { 1035 return ss.stores.command 1036 } 1037 1038 func (ss *SqlSupplier) CommandWebhook() store.CommandWebhookStore { 1039 return ss.stores.commandWebhook 1040 } 1041 1042 func (ss *SqlSupplier) Preference() store.PreferenceStore { 1043 return ss.stores.preference 1044 } 1045 1046 func (ss *SqlSupplier) License() store.LicenseStore { 1047 return ss.stores.license 1048 } 1049 1050 func (ss *SqlSupplier) Token() store.TokenStore { 1051 return ss.stores.token 1052 } 1053 1054 func (ss *SqlSupplier) Emoji() store.EmojiStore { 1055 return ss.stores.emoji 1056 } 1057 1058 func (ss *SqlSupplier) Status() store.StatusStore { 1059 return ss.stores.status 1060 } 1061 1062 func (ss *SqlSupplier) FileInfo() store.FileInfoStore { 1063 return ss.stores.fileInfo 1064 } 1065 1066 func (ss *SqlSupplier) Reaction() store.ReactionStore { 1067 return ss.stores.reaction 1068 } 1069 1070 func (ss *SqlSupplier) Job() store.JobStore { 1071 return ss.stores.job 1072 } 1073 1074 func (ss *SqlSupplier) UserAccessToken() store.UserAccessTokenStore { 1075 return ss.stores.userAccessToken 1076 } 1077 1078 func (ss *SqlSupplier) Role() store.RoleStore { 1079 return ss.stores.role 1080 } 1081 1082 func (ss *SqlSupplier) TermsOfService() store.TermsOfServiceStore { 1083 return ss.stores.TermsOfService 1084 } 1085 1086 func (ss *SqlSupplier) UserTermsOfService() store.UserTermsOfServiceStore { 1087 return ss.stores.UserTermsOfService 1088 } 1089 1090 func (ss *SqlSupplier) Scheme() store.SchemeStore { 1091 return ss.stores.scheme 1092 } 1093 1094 func (ss *SqlSupplier) LinkMetadata() store.LinkMetadataStore { 1095 return ss.stores.linkMetadata 1096 } 1097 1098 func (ss *SqlSupplier) DropAllTables() { 1099 ss.master.TruncateTables() 1100 } 1101 1102 func (ss *SqlSupplier) getQueryBuilder() sq.StatementBuilderType { 1103 builder := sq.StatementBuilder.PlaceholderFormat(sq.Question) 1104 if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { 1105 builder = builder.PlaceholderFormat(sq.Dollar) 1106 } 1107 return builder 1108 } 1109 1110 func (ss *SqlSupplier) CheckIntegrity() <-chan store.IntegrityCheckResult { 1111 results := make(chan store.IntegrityCheckResult) 1112 go CheckRelationalIntegrity(ss, results) 1113 return results 1114 } 1115 1116 type mattermConverter struct{} 1117 1118 func (me mattermConverter) ToDb(val interface{}) (interface{}, error) { 1119 1120 switch t := val.(type) { 1121 case model.StringMap: 1122 return model.MapToJson(t), nil 1123 case map[string]string: 1124 return model.MapToJson(model.StringMap(t)), nil 1125 case model.StringArray: 1126 return model.ArrayToJson(t), nil 1127 case model.StringInterface: 1128 return model.StringInterfaceToJson(t), nil 1129 case map[string]interface{}: 1130 return model.StringInterfaceToJson(model.StringInterface(t)), nil 1131 case JSONSerializable: 1132 return t.ToJson(), nil 1133 case *opengraph.OpenGraph: 1134 return json.Marshal(t) 1135 } 1136 1137 return val, nil 1138 } 1139 1140 func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) { 1141 switch target.(type) { 1142 case *model.StringMap: 1143 binder := func(holder, target interface{}) error { 1144 s, ok := holder.(*string) 1145 if !ok { 1146 return errors.New(utils.T("store.sql.convert_string_map")) 1147 } 1148 b := []byte(*s) 1149 return json.Unmarshal(b, target) 1150 } 1151 return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 1152 case *map[string]string: 1153 binder := func(holder, target interface{}) error { 1154 s, ok := holder.(*string) 1155 if !ok { 1156 return errors.New(utils.T("store.sql.convert_string_map")) 1157 } 1158 b := []byte(*s) 1159 return json.Unmarshal(b, target) 1160 } 1161 return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 1162 case *model.StringArray: 1163 binder := func(holder, target interface{}) error { 1164 s, ok := holder.(*string) 1165 if !ok { 1166 return errors.New(utils.T("store.sql.convert_string_array")) 1167 } 1168 b := []byte(*s) 1169 return json.Unmarshal(b, target) 1170 } 1171 return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 1172 case *model.StringInterface: 1173 binder := func(holder, target interface{}) error { 1174 s, ok := holder.(*string) 1175 if !ok { 1176 return errors.New(utils.T("store.sql.convert_string_interface")) 1177 } 1178 b := []byte(*s) 1179 return json.Unmarshal(b, target) 1180 } 1181 return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 1182 case *map[string]interface{}: 1183 binder := func(holder, target interface{}) error { 1184 s, ok := holder.(*string) 1185 if !ok { 1186 return errors.New(utils.T("store.sql.convert_string_interface")) 1187 } 1188 b := []byte(*s) 1189 return json.Unmarshal(b, target) 1190 } 1191 return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 1192 } 1193 1194 return gorp.CustomScanner{}, false 1195 } 1196 1197 type JSONSerializable interface { 1198 ToJson() string 1199 } 1200 1201 func convertMySQLFullTextColumnsToPostgres(columnNames string) string { 1202 columns := strings.Split(columnNames, ", ") 1203 concatenatedColumnNames := "" 1204 for i, c := range columns { 1205 concatenatedColumnNames += c 1206 if i < len(columns)-1 { 1207 concatenatedColumnNames += " || ' ' || " 1208 } 1209 } 1210 1211 return concatenatedColumnNames 1212 }