2016-07-07 18:54:16 -07:00
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
import (
"errors"
"fmt"
"strconv"
"strings"
2017-07-19 18:47:01 +01:00
"github.com/documize/community/core/env"
2017-07-18 21:55:17 +01:00
"github.com/documize/community/core/streamutil"
2017-07-21 18:14:19 +01:00
"github.com/documize/community/server/web"
2016-07-07 18:54:16 -07:00
)
var dbCheckOK bool // default false
// Check that the database is configured correctly and that all the required tables exist.
2016-07-15 16:54:07 +01:00
// It must be the first function called in this package.
2017-07-20 18:46:11 +01:00
func Check ( runtime * env . Runtime ) bool {
2017-07-21 18:14:19 +01:00
runtime . Log . Info ( "Database checks: started" )
2016-10-09 15:58:43 -07:00
2017-07-19 18:47:01 +01:00
csBits := strings . Split ( runtime . Flags . DBConn , "/" )
2016-07-07 18:54:16 -07:00
if len ( csBits ) > 1 {
web . SiteInfo . DBname = strings . Split ( csBits [ len ( csBits ) - 1 ] , "?" ) [ 0 ]
}
2017-08-30 13:00:07 +01:00
rows , err := runtime . Db . Query ( "SELECT VERSION() AS version, @@version_comment as comment, @@character_set_database AS charset, @@collation_database AS collation" )
2016-07-07 18:54:16 -07:00
if err != nil {
2017-07-21 18:14:19 +01:00
runtime . Log . Error ( "Can't get MySQL configuration" , err )
2016-07-07 18:54:16 -07:00
web . SiteInfo . Issue = "Can't get MySQL configuration: " + err . Error ( )
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
2017-07-18 21:55:17 +01:00
defer streamutil . Close ( rows )
2017-01-17 15:30:39 +00:00
var version , dbComment , charset , collation string
2016-07-07 18:54:16 -07:00
if rows . Next ( ) {
2017-01-17 15:30:39 +00:00
err = rows . Scan ( & version , & dbComment , & charset , & collation )
2016-07-07 18:54:16 -07:00
}
if err == nil {
err = rows . Err ( ) // get any error encountered during iteration
}
if err != nil {
2017-07-21 18:14:19 +01:00
runtime . Log . Error ( "no MySQL configuration returned" , err )
2016-07-07 18:54:16 -07:00
web . SiteInfo . Issue = "no MySQL configuration return issue: " + err . Error ( )
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
2018-09-12 20:03:34 +01:00
// runtime.DbVariant = GetSQLVariant(runtime.Flags.DBType, dbComment)
2016-07-07 18:54:16 -07:00
2018-09-12 20:03:34 +01:00
runtime . Log . Info ( fmt . Sprintf ( "Database checks: SQL variant %v" , runtime . Storage . Type ) )
2017-07-21 18:14:19 +01:00
runtime . Log . Info ( "Database checks: SQL version " + version )
2017-01-17 15:30:39 +00:00
verNums , err := GetSQLVersion ( version )
if err != nil {
2017-07-21 18:14:19 +01:00
runtime . Log . Error ( "Database version check failed" , err )
2017-01-17 15:30:39 +00:00
}
// Check minimum MySQL version as we need JSON column type.
verInts := [ ] int { 5 , 7 , 10 } // Minimum MySQL version
2018-09-12 20:03:34 +01:00
if runtime . Storage . Type == env . StoreTypeMariaDB {
2017-08-09 16:18:03 +01:00
verInts = [ ] int { 10 , 3 , 0 } // Minimum MariaDB version
2017-01-17 15:30:39 +00:00
}
for k , v := range verInts {
2018-05-10 15:14:50 +01:00
// If major release is higher then skip minor/patch checks (e.g. 8.x.x > 5.x.x)
if k == 0 && len ( verNums ) > 0 && verNums [ 0 ] > verInts [ 0 ] {
break
}
2017-01-17 15:30:39 +00:00
if verNums [ k ] < v {
want := fmt . Sprintf ( "%d.%d.%d" , verInts [ 0 ] , verInts [ 1 ] , verInts [ 2 ] )
2017-07-21 18:14:19 +01:00
runtime . Log . Error ( "MySQL version element " + strconv . Itoa ( k + 1 ) + " of '" + version + "' not high enough, need at least version " + want , errors . New ( "bad MySQL version" ) )
2017-01-17 15:30:39 +00:00
web . SiteInfo . Issue = "MySQL version element " + strconv . Itoa ( k + 1 ) + " of '" + version + "' not high enough, need at least version " + want
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
}
{ // check the MySQL character set and collation
2017-08-07 14:42:02 +01:00
if charset != "utf8" && charset != "utf8mb4" {
2017-08-08 18:21:38 +01:00
runtime . Log . Error ( "MySQL character set not utf8/utf8mb4:" , errors . New ( charset ) )
web . SiteInfo . Issue = "MySQL character set not utf8/utf8mb4: " + charset
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
if ! strings . HasPrefix ( collation , "utf8" ) {
2017-07-21 18:14:19 +01:00
runtime . Log . Error ( "MySQL collation sequence not utf8...:" , errors . New ( collation ) )
2016-07-07 18:54:16 -07:00
web . SiteInfo . Issue = "MySQL collation sequence not utf8...: " + collation
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
}
{ // if there are no rows in the database, enter set-up mode
var flds [ ] string
2017-07-19 18:47:01 +01:00
if err := runtime . Db . Select ( & flds ,
2016-07-07 18:54:16 -07:00
` SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = ' ` + web . SiteInfo . DBname +
` ' and TABLE_TYPE='BASE TABLE' ` ) ; err != nil {
2017-07-21 18:14:19 +01:00
runtime . Log . Error ( "Can't get MySQL number of tables" , err )
2016-07-07 18:54:16 -07:00
web . SiteInfo . Issue = "Can't get MySQL number of tables: " + err . Error ( )
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
if strings . TrimSpace ( flds [ 0 ] ) == "0" {
2017-07-21 18:14:19 +01:00
runtime . Log . Info ( "Entering database set-up mode because the database is empty....." )
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeSetup
2016-07-07 18:54:16 -07:00
return false
}
}
{ // check all the required tables exist
var tables = [ ] string { ` account ` ,
2017-09-12 09:59:43 +01:00
` attachment ` , ` document ` ,
` label ` , ` organization ` ,
2016-07-07 18:54:16 -07:00
` page ` , ` revision ` , ` search ` , ` user ` }
for _ , table := range tables {
var dummy [ ] string
2017-07-19 18:47:01 +01:00
if err := runtime . Db . Select ( & dummy , "SELECT 1 FROM " + table + " LIMIT 1;" ) ; err != nil {
2017-07-21 18:14:19 +01:00
runtime . Log . Error ( "Entering bad database mode because: SELECT 1 FROM " + table + " LIMIT 1;" , err )
2016-07-07 18:54:16 -07:00
web . SiteInfo . Issue = "MySQL database is not empty, but does not contain table: " + table
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
}
}
2017-07-24 16:24:21 +01:00
runtime . Flags . SiteMode = env . SiteModeNormal // actually no need to do this (as already ""), this for documentation
2017-07-19 18:47:01 +01:00
web . SiteInfo . DBname = "" // do not give this info when not in set-up mode
2016-07-07 18:54:16 -07:00
dbCheckOK = true
return true
}
2016-10-09 15:58:43 -07:00
2017-01-17 15:30:39 +00:00
// GetSQLVersion returns SQL version as major,minor,patch numerics.
func GetSQLVersion ( v string ) ( ints [ ] int , err error ) {
ints = [ ] int { 0 , 0 , 0 }
pos := strings . Index ( v , "-" )
if pos > 1 {
v = v [ : pos ]
}
vs := strings . Split ( v , "." )
if len ( vs ) < 3 {
err = errors . New ( "MySQL version not of the form a.b.c" )
return
}
for key , val := range vs {
num , err := strconv . Atoi ( val )
2016-10-09 15:58:43 -07:00
if err != nil {
2017-01-17 15:30:39 +00:00
return ints , err
2016-10-09 15:58:43 -07:00
}
2017-01-17 15:30:39 +00:00
ints [ key ] = num
2016-10-09 15:58:43 -07:00
}
return
}