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

@ -34,7 +34,7 @@ type columsDefinition struct {
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
@ -63,69 +63,256 @@ 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", Name: "user",
Columns: []columsDefinition{ Columns: []columsDefinition{
{ {
Name: "id", Name: "id",
Type: "int(11)", Type: "int(11)",
Default: nil,
IsNullable: false, IsNullable: false,
Extra: "AUTO_INCREMENT", AutoIncrement: true,
}, },
{ {
Name: "name", Name: "name",
Type: "varchar(256)", Type: "varchar(256)",
Default: nil,
IsNullable: false, IsNullable: false,
Extra: "",
Charset: "utf8mb4", Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci", Collation: "utf8mb4_unicode_ci",
}, },
{ {
Name: "password", Name: "password",
Type: "varchar(256)", Type: "varchar(256)",
Default: nil,
IsNullable: false, IsNullable: false,
Extra: "",
Charset: "utf8mb4", Charset: "utf8mb4",
Collation: "utf8mb4_unicode", Collation: "utf8mb4_bin",
}, },
{ {
Name: "password-salt", Name: "password_salt",
Type: "varchar(256)", Type: "varchar(256)",
Default: nil,
IsNullable: false, IsNullable: false,
Extra: "",
Charset: "utf8mb4", Charset: "utf8mb4",
Collation: "utf8mb4_unicode", Collation: "utf8mb4_bin",
}, },
{ {
Name: "permission_level", Name: "permission_level",
Type: "enum('ADMIN', 'MODERATOR', 'USER')", Type: "enum('ADMIN', 'MODERATOR', 'USER')",
Default: "USER", Default: "'USER'",
IsNullable: false, IsNullable: false,
Extra: "",
Charset: nil, Charset: nil,
}, },
{ {
Name: "email", Name: "email",
Type: "varchar(256)", Type: "varchar(256)",
Default: nil,
IsNullable: true, IsNullable: true,
Extra: "", Default: "NULL",
Charset: "utf8mb4", Charset: "utf8mb4",
Collation: "utf8mb4_unicode", 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: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "user",
Columns: []string{
"id",
},
},
{
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "folder",
Columns: []string{
"id",
},
},
{
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "tag",
Columns: []string{
"id",
},
},
{
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "website",
Columns: []string{
"id",
},
},
{
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "tags_websites",
Columns: []string{
"tag_id",
"website_id",
},
},
*/
// UNIQUE KEYS
{
Name: "UNIQUE_user_name",
Type: database_ENUM_INDEX_UNIQUE,
Table: "user",
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",
},
},
} }
var dbPath string var dbPath string
@ -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)
}
}