Starting auto-creation of database
parent
e1baee2fd3
commit
b6104dfdbd
|
@ -34,7 +34,7 @@ type columsDefinition struct {
|
|||
Type string
|
||||
Default any
|
||||
IsNullable bool
|
||||
Extra string
|
||||
AutoIncrement bool
|
||||
Charset any
|
||||
Collation any
|
||||
Table string
|
||||
|
@ -63,69 +63,256 @@ type tableDefinition struct {
|
|||
}
|
||||
|
||||
type databaseDefinition struct {
|
||||
Name string
|
||||
Tables []tableDefinition
|
||||
Indexes []indexDefinition
|
||||
ForeignKeys []foreignKeyDefinition
|
||||
}
|
||||
|
||||
var listTables = []tableDefinition{
|
||||
var databaseStructure databaseDefinition = databaseDefinition{
|
||||
Tables: []tableDefinition{
|
||||
{
|
||||
Name: "users",
|
||||
Name: "user",
|
||||
Columns: []columsDefinition{
|
||||
{
|
||||
Name: "id",
|
||||
Type: "int(11)",
|
||||
Default: nil,
|
||||
IsNullable: false,
|
||||
Extra: "AUTO_INCREMENT",
|
||||
AutoIncrement: true,
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Type: "varchar(256)",
|
||||
Default: nil,
|
||||
IsNullable: false,
|
||||
Extra: "",
|
||||
Charset: "utf8mb4",
|
||||
Collation: "utf8mb4_unicode_ci",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Type: "varchar(256)",
|
||||
Default: nil,
|
||||
IsNullable: false,
|
||||
Extra: "",
|
||||
Charset: "utf8mb4",
|
||||
Collation: "utf8mb4_unicode",
|
||||
Collation: "utf8mb4_bin",
|
||||
},
|
||||
{
|
||||
Name: "password-salt",
|
||||
Name: "password_salt",
|
||||
Type: "varchar(256)",
|
||||
Default: nil,
|
||||
IsNullable: false,
|
||||
Extra: "",
|
||||
Charset: "utf8mb4",
|
||||
Collation: "utf8mb4_unicode",
|
||||
Collation: "utf8mb4_bin",
|
||||
},
|
||||
{
|
||||
Name: "permission_level",
|
||||
Type: "enum('ADMIN', 'MODERATOR', 'USER')",
|
||||
Default: "USER",
|
||||
Default: "'USER'",
|
||||
IsNullable: false,
|
||||
Extra: "",
|
||||
Charset: nil,
|
||||
},
|
||||
{
|
||||
Name: "email",
|
||||
Type: "varchar(256)",
|
||||
Default: nil,
|
||||
IsNullable: true,
|
||||
Extra: "",
|
||||
Default: "NULL",
|
||||
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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue