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 {
Name string
Type string
Default any
IsNullable bool
Extra string
Charset any
Collation any
Table string
Name string
Type string
Default any
IsNullable bool
AutoIncrement bool
Charset any
Collation any
Table string
}
type indexDefinition struct {
@ -63,68 +63,255 @@ type tableDefinition struct {
}
type databaseDefinition struct {
Name string
Tables []tableDefinition
Indexes []indexDefinition
ForeignKeys []foreignKeyDefinition
}
var listTables = []tableDefinition{
{
Name: "users",
Columns: []columsDefinition{
var databaseStructure databaseDefinition = databaseDefinition{
Tables: []tableDefinition{
{
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",
Type: "int(11)",
Default: nil,
IsNullable: false,
Extra: "AUTO_INCREMENT",
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "user",
Columns: []string{
"id",
},
},
{
Name: "name",
Type: "varchar(256)",
Default: nil,
IsNullable: false,
Extra: "",
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "folder",
Columns: []string{
"id",
},
},
{
Name: "password",
Type: "varchar(256)",
Default: nil,
IsNullable: false,
Extra: "",
Charset: "utf8mb4",
Collation: "utf8mb4_unicode",
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "tag",
Columns: []string{
"id",
},
},
{
Name: "password-salt",
Type: "varchar(256)",
Default: nil,
IsNullable: false,
Extra: "",
Charset: "utf8mb4",
Collation: "utf8mb4_unicode",
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "website",
Columns: []string{
"id",
},
},
{
Name: "permission_level",
Type: "enum('ADMIN', 'MODERATOR', 'USER')",
Default: "USER",
IsNullable: false,
Extra: "",
Charset: nil,
Name: "",
Type: database_ENUM_INDEX_PRIMARY,
Table: "tags_websites",
Columns: []string{
"tag_id",
"website_id",
},
},
{
Name: "email",
Type: "varchar(256)",
Default: nil,
IsNullable: true,
Extra: "",
Charset: "utf8mb4",
Collation: "utf8mb4_unicode",
*/
// 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",
},
},
}
@ -146,6 +333,8 @@ func Init() {
profiler.Register(profiler_DATABASE_QUERY, 4000, "µs")
isInit = true
destroyDatabase()
updateDatabaseStructure()
initDatabaseStructure()
}
@ -247,13 +436,47 @@ func executeQuery(query string, args ...any) *sql.Rows {
profiler.Add(profiler_DATABASE_QUERY, time.Duration(time.Since(tStart).Microseconds()))
if err != nil {
log.Printf("Cannot query the database ! (Query : %s) with args ", query)
log.Fatalln(args...)
log.Printf("Cannot query the database ! Query : %s", query)
log.Fatalln("With args :", args)
}
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() {
storedRoutines = nil
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 {
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)
if rows != nil {
defer rows.Close()
@ -332,19 +556,18 @@ func fetchTables() []string {
}
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)
if rows != nil {
defer rows.Close()
for rows.Next() {
var resName, resType, resDefault, resNullable, resExtra, resCharset, resCollation string
rows.Scan(&resName, &resType, &resDefault, &resNullable, &resExtra, &resCharset, &resCollation)
var resName, resType, resDefault, resNullable, resCharset, resCollation string
rows.Scan(&resName, &resType, &resDefault, &resNullable, &resCharset, &resCollation)
columns = append(columns, columsDefinition{
Name: resName,
Type: resType,
Default: resDefault,
IsNullable: resNullable == "YES",
Extra: resExtra,
Charset: resCharset,
Collation: resCollation,
Table: table,
@ -421,8 +644,8 @@ func initDatabaseStructure() {
var columns []columsDefinition = fetchColumns(tables[i])
log.Printf(" - Table %s : \n", tables[i])
for j := range columns {
log.Printf(" - Column %s %s IsNullable:%t Default:%s Extra:%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)
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].Charset, columns[j].Collation)
}
}
@ -438,3 +661,119 @@ func initDatabaseStructure() {
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)
}
}