Starting auto-creation of database

dev
Nogard 2025-05-03 20:32:45 +02:00
parent e1baee2fd3
commit b6104dfdbd
1 changed files with 401 additions and 62 deletions

View File

@ -30,14 +30,14 @@ const (
) )
type columsDefinition struct { type columsDefinition struct {
Name string Name string
Type string Type string
Default any Default any
IsNullable bool IsNullable bool
Extra string AutoIncrement bool
Charset any Charset any
Collation any Collation any
Table string Table string
} }
type indexDefinition struct { type indexDefinition struct {
@ -63,68 +63,255 @@ type tableDefinition struct {
} }
type databaseDefinition struct { type databaseDefinition struct {
Name string
Tables []tableDefinition Tables []tableDefinition
Indexes []indexDefinition Indexes []indexDefinition
ForeignKeys []foreignKeyDefinition ForeignKeys []foreignKeyDefinition
} }
var listTables = []tableDefinition{ var databaseStructure databaseDefinition = databaseDefinition{
{ Tables: []tableDefinition{
Name: "users", {
Columns: []columsDefinition{ Name: "user",
Columns: []columsDefinition{
{
Name: "id",
Type: "int(11)",
IsNullable: false,
AutoIncrement: true,
},
{
Name: "name",
Type: "varchar(256)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
},
{
Name: "password",
Type: "varchar(256)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_bin",
},
{
Name: "password_salt",
Type: "varchar(256)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_bin",
},
{
Name: "permission_level",
Type: "enum('ADMIN', 'MODERATOR', 'USER')",
Default: "'USER'",
IsNullable: false,
Charset: nil,
},
{
Name: "email",
Type: "varchar(256)",
IsNullable: true,
Default: "NULL",
Charset: "utf8mb4",
Collation: "utf8mb4_bin",
},
},
},
{
Name: "folder",
Columns: []columsDefinition{
{
Name: "id",
Type: "int(11)",
IsNullable: false,
AutoIncrement: true,
},
{
Name: "name",
Type: "varchar(64)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
},
},
},
{
Name: "tag",
Columns: []columsDefinition{
{
Name: "id",
Type: "int(11)",
IsNullable: false,
AutoIncrement: true,
},
{
Name: "name",
Type: "varchar(64)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
},
},
},
{
Name: "website",
Columns: []columsDefinition{
{
Name: "id",
Type: "int(11)",
IsNullable: false,
AutoIncrement: true,
},
{
Name: "name",
Type: "varchar(256)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
},
{
Name: "content",
Type: "longtext",
IsNullable: true,
Default: "NULL",
Charset: "utf8mb4",
Collation: "utf8mb4_bin",
},
{
Name: "description",
Type: "longtext",
IsNullable: true,
Default: "NULL",
Charset: "utf8mb4",
Collation: "utf8mb4_bin",
},
{
Name: "folder_id",
Type: "int(11)",
IsNullable: false,
},
{
Name: "date",
Type: "datetime",
IsNullable: false,
Default: "current_timestamp()",
},
{
Name: "title",
Type: "varchar(256)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
},
},
},
{
Name: "tags_websites",
Columns: []columsDefinition{
{
Name: "tag_id",
Type: "int(10)",
IsNullable: false,
},
{
Name: "website_id",
Type: "int(10)",
IsNullable: false,
},
{
Name: "value",
Type: "varchar(64)",
IsNullable: false,
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
},
},
},
},
Indexes: []indexDefinition{
// PRIMARY KEYS
/*
{ {
Name: "id", Name: "",
Type: "int(11)", Type: database_ENUM_INDEX_PRIMARY,
Default: nil, Table: "user",
IsNullable: false, Columns: []string{
Extra: "AUTO_INCREMENT", "id",
},
}, },
{ {
Name: "name", Name: "",
Type: "varchar(256)", Type: database_ENUM_INDEX_PRIMARY,
Default: nil, Table: "folder",
IsNullable: false, Columns: []string{
Extra: "", "id",
Charset: "utf8mb4", },
Collation: "utf8mb4_unicode_ci",
}, },
{ {
Name: "password", Name: "",
Type: "varchar(256)", Type: database_ENUM_INDEX_PRIMARY,
Default: nil, Table: "tag",
IsNullable: false, Columns: []string{
Extra: "", "id",
Charset: "utf8mb4", },
Collation: "utf8mb4_unicode",
}, },
{ {
Name: "password-salt", Name: "",
Type: "varchar(256)", Type: database_ENUM_INDEX_PRIMARY,
Default: nil, Table: "website",
IsNullable: false, Columns: []string{
Extra: "", "id",
Charset: "utf8mb4", },
Collation: "utf8mb4_unicode",
}, },
{ {
Name: "permission_level", Name: "",
Type: "enum('ADMIN', 'MODERATOR', 'USER')", Type: database_ENUM_INDEX_PRIMARY,
Default: "USER", Table: "tags_websites",
IsNullable: false, Columns: []string{
Extra: "", "tag_id",
Charset: nil, "website_id",
},
}, },
{ */
Name: "email", // UNIQUE KEYS
Type: "varchar(256)",
Default: nil, {
IsNullable: true, Name: "UNIQUE_user_name",
Extra: "", Type: database_ENUM_INDEX_UNIQUE,
Charset: "utf8mb4", Table: "user",
Collation: "utf8mb4_unicode", Columns: []string{
"name",
}, },
}, },
{
Name: "UNIQUE_website_name_folder",
Type: database_ENUM_INDEX_UNIQUE,
Table: "website",
Columns: []string{
"name",
"folder_id",
},
},
},
ForeignKeys: []foreignKeyDefinition{
{
Name: "FK_tags_websites__tag_id",
Table: "tags_websites",
ColumnName: "tag_id",
PointingToTable: "tag",
PointingToColumn: "id",
UpdateRule: "CASCADE",
DeleteRule: "CASCADE",
},
{
Name: "FK_tags_websites__website_id",
Table: "tags_websites",
ColumnName: "website_id",
PointingToTable: "website",
PointingToColumn: "id",
UpdateRule: "CASCADE",
DeleteRule: "CASCADE",
},
}, },
} }
@ -146,6 +333,8 @@ func Init() {
profiler.Register(profiler_DATABASE_QUERY, 4000, "µs") profiler.Register(profiler_DATABASE_QUERY, 4000, "µs")
isInit = true isInit = true
destroyDatabase()
updateDatabaseStructure()
initDatabaseStructure() initDatabaseStructure()
} }
@ -247,13 +436,47 @@ func executeQuery(query string, args ...any) *sql.Rows {
profiler.Add(profiler_DATABASE_QUERY, time.Duration(time.Since(tStart).Microseconds())) profiler.Add(profiler_DATABASE_QUERY, time.Duration(time.Since(tStart).Microseconds()))
if err != nil { if err != nil {
log.Printf("Cannot query the database ! (Query : %s) with args ", query) log.Printf("Cannot query the database ! Query : %s", query)
log.Fatalln(args...) log.Fatalln("With args :", args)
} }
return rows return rows
} }
func executeExec(query string, args ...any) sql.Result {
if !isInit {
return nil
}
if db == nil {
connect()
}
var err error
var result sql.Result
tStart := time.Now()
for i := range dbConfig.RetriesOnError {
result, err = db.Exec(query, args...)
if err == nil {
break
}
log.Printf("Error while executing on the database [%d/%d] : %s\n", i+1, dbConfig.RetriesOnError, err)
if i+1 == dbConfig.RetriesOnError {
break
}
time.Sleep((time.Duration)(dbConfig.TimeBetweenRetriesMs) * time.Millisecond)
ping()
}
profiler.Add(profiler_DATABASE_QUERY, time.Duration(time.Since(tStart).Microseconds()))
if err != nil {
log.Printf("Cannot execute on the database ! Command : %s", query)
log.Fatalln("With args : ", args)
}
return result
}
func fetchStoredRoutines() { func fetchStoredRoutines() {
storedRoutines = nil storedRoutines = nil
var rows *sql.Rows = executeQuery("SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA=?;", dbConfig.Database) var rows *sql.Rows = executeQuery("SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA=?;", dbConfig.Database)
@ -318,7 +541,8 @@ func callStoredFunction(name string, args ...any) {
} }
func fetchTables() []string { func fetchTables() []string {
var rows *sql.Rows = executeQuery("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=?;", dbConfig.Database) var rows *sql.Rows = executeQuery("SHOW TABLES;")
//var rows *sql.Rows = executeQuery("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=?;", dbConfig.Database)
var tables []string = make([]string, 0) var tables []string = make([]string, 0)
if rows != nil { if rows != nil {
defer rows.Close() defer rows.Close()
@ -332,19 +556,18 @@ func fetchTables() []string {
} }
func fetchColumns(table string) []columsDefinition { func fetchColumns(table string) []columsDefinition {
var rows *sql.Rows = executeQuery("SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE, EXTRA, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=?;", dbConfig.Database, table) var rows *sql.Rows = executeQuery("SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=?;", dbConfig.Database, table)
var columns []columsDefinition = make([]columsDefinition, 0) var columns []columsDefinition = make([]columsDefinition, 0)
if rows != nil { if rows != nil {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var resName, resType, resDefault, resNullable, resExtra, resCharset, resCollation string var resName, resType, resDefault, resNullable, resCharset, resCollation string
rows.Scan(&resName, &resType, &resDefault, &resNullable, &resExtra, &resCharset, &resCollation) rows.Scan(&resName, &resType, &resDefault, &resNullable, &resCharset, &resCollation)
columns = append(columns, columsDefinition{ columns = append(columns, columsDefinition{
Name: resName, Name: resName,
Type: resType, Type: resType,
Default: resDefault, Default: resDefault,
IsNullable: resNullable == "YES", IsNullable: resNullable == "YES",
Extra: resExtra,
Charset: resCharset, Charset: resCharset,
Collation: resCollation, Collation: resCollation,
Table: table, Table: table,
@ -421,8 +644,8 @@ func initDatabaseStructure() {
var columns []columsDefinition = fetchColumns(tables[i]) var columns []columsDefinition = fetchColumns(tables[i])
log.Printf(" - Table %s : \n", tables[i]) log.Printf(" - Table %s : \n", tables[i])
for j := range columns { for j := range columns {
log.Printf(" - Column %s %s IsNullable:%t Default:%s Extra:%s Charset:%s/%s\n", log.Printf(" - Column %s %s IsNullable:%t Default:%s Charset:%s/%s\n",
columns[j].Name, columns[j].Type, columns[j].IsNullable, columns[j].Default, columns[j].Extra, columns[j].Charset, columns[j].Collation) columns[j].Name, columns[j].Type, columns[j].IsNullable, columns[j].Default, columns[j].Charset, columns[j].Collation)
} }
} }
@ -438,3 +661,119 @@ func initDatabaseStructure() {
log.Printf(" - Foreign key %s\n", foreignKeys[i]) log.Printf(" - Foreign key %s\n", foreignKeys[i])
} }
} }
func updateDatabaseStructure() {
var tablesNeeded []tableDefinition = databaseStructure.Tables
for t := range tablesNeeded {
var table tableDefinition = tablesNeeded[t]
var columnsNeeded []columsDefinition = table.Columns
executeExec(fmt.Sprintf("DROP TABLE IF EXISTS %s;", table.Name))
var query string = fmt.Sprintf("CREATE TABLE %s (\n", table.Name)
for c := range columnsNeeded {
if c != 0 {
query += ",\n"
}
var column columsDefinition = columnsNeeded[c]
query += fmt.Sprintf("%s %s", column.Name, column.Type)
str, ok := column.Charset.(string)
if ok {
query += fmt.Sprintf(" CHARACTER SET %s", str)
}
str, ok = column.Collation.(string)
if ok {
query += fmt.Sprintf(" COLLATE %s", str)
}
if !column.IsNullable {
query += " NOT NULL"
}
str, ok = column.Default.(string)
if ok {
query += fmt.Sprintf(" DEFAULT %s", str)
}
}
query += "\n);"
fmt.Println(query)
executeExec(query)
}
var indexesNeeded []indexDefinition = databaseStructure.Indexes
for i := range indexesNeeded {
var index indexDefinition = indexesNeeded[i]
// ALTER TABLE <table> ADD CONSTRAINT PRIMARY KEY(<columns>);
// ALTER TABLE <table> ADD CONSTRAINT <constraint_name> UNIQUE(<columns>);
var command string = fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT ", index.Table)
switch index.Type {
case database_ENUM_INDEX_PRIMARY:
command += "PRIMARY KEY("
case database_ENUM_INDEX_UNIQUE:
command += fmt.Sprintf(" %s UNIQUE(", index.Name)
}
for c := range index.Columns {
if c != 0 {
command += ", "
}
var column string = index.Columns[c]
command += column
}
command += ");"
fmt.Println(command)
executeExec(command)
}
var foreignKeys []foreignKeyDefinition = databaseStructure.ForeignKeys
for fk := range foreignKeys {
var foreignKey foreignKeyDefinition = foreignKeys[fk]
// ALTER TABLE <table> ADD CONSTRAINT <constraint_name> FOREIGN KEY(<column>) REFERENCES <ref_table>(<ref_column>) ON DELETE <CASCADE / SET NULL> UPDATE <CASCADE / SET NULL>;
var command string = fmt.Sprintf("ALTER TABLE %s ADD INDEX %s(%s);", foreignKey.PointingToTable, foreignKey.Name, foreignKey.PointingToColumn)
fmt.Println(command)
executeExec(command)
command = fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)", foreignKey.Table, foreignKey.Name, foreignKey.ColumnName, foreignKey.PointingToTable, foreignKey.PointingToColumn)
if foreignKey.DeleteRule != "" {
command += fmt.Sprintf(" ON DELETE %s", foreignKey.DeleteRule)
}
if foreignKey.UpdateRule != "" {
command += fmt.Sprintf(" ON UPDATE %s", foreignKey.UpdateRule)
}
command += ";"
fmt.Println(command)
executeExec(command)
}
}
func destroyDatabase() {
var tables []string = fetchTables()
var foreignKeys []foreignKeyDefinition = fetchForeignKeys()
for fk := range foreignKeys {
var foreignKey foreignKeyDefinition = foreignKeys[fk]
var command string = fmt.Sprintf("ALTER TABLE %s DROP FOREIGN KEY %s;", foreignKey.Table, foreignKey.Name)
fmt.Println(command)
executeExec(command)
}
for t := range tables {
var table string = tables[t]
var command string = fmt.Sprintf("DROP TABLE %s;", table)
fmt.Println(command)
executeExec(command)
}
}