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"
2016-07-20 15:58:37 +01:00
"github.com/documize/community/core/log"
2017-07-18 21:55:17 +01:00
"github.com/documize/community/core/streamutil"
2016-10-09 15:58:43 -07:00
"github.com/documize/community/core/web"
2016-07-07 18:54:16 -07:00
"github.com/jmoiron/sqlx"
)
2017-01-17 15:30:39 +00:00
// sql variantsa
const sqlVariantMySQL string = "MySQL"
const sqlVariantPercona string = "Percona"
const sqlVariantMariaDB string = "MariaDB"
2016-07-07 18:54:16 -07:00
var dbCheckOK bool // default false
// dbPtr is a pointer to the central connection to the database, used by all database requests.
2017-07-19 18:47:01 +01:00
var dbPtr * sqlx . DB
2016-07-07 18:54:16 -07:00
// 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-19 18:47:01 +01:00
func Check ( runtime env . Runtime ) bool {
dbPtr = runtime . Db
2016-07-07 18:54:16 -07:00
2017-03-19 14:25:21 +00:00
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-07-19 18:47:01 +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 {
log . Error ( "Can't get MySQL configuration" , err )
web . SiteInfo . Issue = "Can't get MySQL configuration: " + err . Error ( )
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . 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
}
2017-01-17 15:30:39 +00:00
2016-07-07 18:54:16 -07:00
if err == nil {
err = rows . Err ( ) // get any error encountered during iteration
}
2017-01-17 15:30:39 +00:00
2016-07-07 18:54:16 -07:00
if err != nil {
log . Error ( "no MySQL configuration returned" , err )
web . SiteInfo . Issue = "no MySQL configuration return issue: " + err . Error ( )
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
2017-01-17 15:30:39 +00:00
// Get SQL variant as this affects minimum version checking logic.
// MySQL and Percona share same version scheme (e..g 5.7.10).
// MariaDB starts at 10.2.x
sqlVariant := GetSQLVariant ( dbComment )
2017-03-19 14:25:21 +00:00
log . Info ( "Database checks: SQL variant " + sqlVariant )
log . Info ( "Database checks: SQL version " + version )
2017-01-17 15:30:39 +00:00
verNums , err := GetSQLVersion ( version )
if err != nil {
log . Error ( "Database version check failed" , err )
}
// Check minimum MySQL version as we need JSON column type.
verInts := [ ] int { 5 , 7 , 10 } // Minimum MySQL version
if sqlVariant == sqlVariantMariaDB {
verInts = [ ] int { 10 , 2 , 0 } // Minimum MariaDB version
}
for k , v := range verInts {
if verNums [ k ] < v {
want := fmt . Sprintf ( "%d.%d.%d" , verInts [ 0 ] , verInts [ 1 ] , verInts [ 2 ] )
log . Error ( "MySQL version element " + strconv . Itoa ( k + 1 ) + " of '" + version + "' not high enough, need at least version " + want , errors . New ( "bad MySQL version" ) )
web . SiteInfo . Issue = "MySQL version element " + strconv . Itoa ( k + 1 ) + " of '" + version + "' not high enough, need at least version " + want
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
}
{ // check the MySQL character set and collation
if charset != "utf8" {
log . Error ( "MySQL character set not utf8:" , errors . New ( charset ) )
web . SiteInfo . Issue = "MySQL character set not utf8: " + charset
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
if ! strings . HasPrefix ( collation , "utf8" ) {
log . Error ( "MySQL collation sequence not utf8...:" , errors . New ( collation ) )
web . SiteInfo . Issue = "MySQL collation sequence not utf8...: " + collation
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . 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 {
log . Error ( "Can't get MySQL number of tables" , err )
web . SiteInfo . Issue = "Can't get MySQL number of tables: " + err . Error ( )
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
if strings . TrimSpace ( flds [ 0 ] ) == "0" {
2016-11-27 15:59:08 -08:00
log . Info ( "Entering database set-up mode because the database is empty....." )
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . SiteModeSetup
2016-07-07 18:54:16 -07:00
return false
}
}
{ // check all the required tables exist
var tables = [ ] string { ` account ` ,
` attachment ` , ` audit ` , ` document ` ,
` label ` , ` labelrole ` , ` organization ` ,
` 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 {
2016-07-07 18:54:16 -07:00
log . Error ( "Entering bad database mode because: SELECT 1 FROM " + table + " LIMIT 1;" , err )
web . SiteInfo . Issue = "MySQL database is not empty, but does not contain table: " + table
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . SiteModeBadDB
2016-07-07 18:54:16 -07:00
return false
}
}
}
2017-07-19 18:47:01 +01:00
runtime . Flags . SiteMode = web . SiteModeNormal // actually no need to do this (as already ""), this for documentation
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
// GetSQLVariant uses database value form @@version_comment to deduce MySQL variant.
func GetSQLVariant ( vc string ) string {
vc = strings . ToLower ( vc )
2016-10-09 15:58:43 -07:00
2017-01-17 15:30:39 +00:00
if strings . Contains ( vc , "mariadb" ) {
return sqlVariantMariaDB
} else if strings . Contains ( vc , "percona" ) {
return sqlVariantPercona
} else if strings . Contains ( vc , "mysql" ) {
return sqlVariantMySQL
}
2016-10-09 15:58:43 -07:00
2017-01-17 15:30:39 +00:00
return "UNKNOWN"
}
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
}