Database : Add decoder for stored procedures results

This commit is contained in:
Nogard 2025-05-11 18:23:00 +02:00
parent 4e9c083b0e
commit 435a12a6bb
2 changed files with 208 additions and 47 deletions

View File

@ -25,7 +25,7 @@ func Init(router *httprouter.Router) {
router.GET("/", apiHandler(apiRoot))
router.GET(apiRootPath+"/auth", apiHandler(apiAuth))
router.GET(apiRootPath+"/auth/login", apiHandler(apiAuthLogin))
router.POST(apiRootPath+"/auth/login", apiHandler(apiAuthLogin))
router.GET(apiRootPath+"/auth/logout", apiHandler(apiAuthLogout))
router.GET(apiRootPath+"/page", apiHandler(apiPage))
@ -34,6 +34,7 @@ func Init(router *httprouter.Router) {
router.GET(apiRootPath+"/page/:folder/:page/content", apiHandler(apiPageFolderPageContent))
router.GET(apiRootPath+"/user", apiHandler(apiUser))
router.POST(apiRootPath+"/user/:name", apiHandler(apiUser_Create))
router.GET(apiRootPath+"/user/:name", apiHandler(apiUserName))
router.GET(apiRootPath+"/tag", apiHandler(apiTag))
@ -72,6 +73,18 @@ func apiAuth(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
func apiAuthLogin(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprintf(w, "API Auth Login")
r.ParseForm()
var username string = r.FormValue("username")
var password string = r.FormValue("password")
fmt.Fprintf(w, "Login : User : %s, Password : %s\n\n", username, password)
var result database.DatabaseResult = database.ExecuteStoredRoutine("CheckUser", username, password)
database.DecodeDatabaseResult(&result)
fmt.Println(result)
if result.Error.HasError() {
fmt.Fprintf(w, "Error : %s\n\n", &result.Error)
}
}
func apiAuthLogout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@ -100,10 +113,29 @@ func apiPageFolderPageContent(w http.ResponseWriter, r *http.Request, params htt
func apiUser(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprintf(w, "API User")
var result database.DatabaseResult = database.ExecuteStoredRoutine("ListUsers")
database.DecodeDatabaseResult(&result)
fmt.Println(result)
}
func apiUser_Create(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
r.ParseForm()
var username string = params.ByName("name")
var password string = r.FormValue("password")
fmt.Fprintf(w, "API User Create\n")
fmt.Fprintf(w, "User : %s, Password : %s\n\n", username, password)
var result database.DatabaseResult = database.ExecuteStoredRoutine("CreateUser", username, password)
database.DecodeDatabaseResult(&result)
fmt.Println(result)
if result.Error.HasError() {
fmt.Fprintf(w, "Error : %s\n\n", &result.Error)
}
}
func apiUserName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Fprintf(w, "API User Name(%s)", params.ByName("page"))
fmt.Fprintf(w, "API User Name(%s)", params.ByName("name"))
}
// API Tag
@ -113,7 +145,7 @@ func apiTag(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
}
func apiTagName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Fprintf(w, "API Tag Name(%s)", params.ByName("page"))
fmt.Fprintf(w, "API Tag Name(%s)", params.ByName("name"))
}
// API Debug Profiler
@ -129,7 +161,7 @@ func apiDebugProfiler(w http.ResponseWriter, r *http.Request, _ httprouter.Param
}
func apiDebugProfilerName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Fprintf(w, "API Debug Profiler(%s)\n%s", params.ByName("page"), string(profiler.Get(params.ByName("page")).ToString()))
fmt.Fprintf(w, "API Debug Profiler(%s)\n%s", params.ByName("name"), string(profiler.Get(params.ByName("name")).ToString()))
}
// API Debug Database

View File

@ -12,6 +12,32 @@ import (
_ "github.com/go-sql-driver/mysql"
)
type DatabaseError struct {
message string
}
func createDatabaseError(message string) DatabaseError {
log.Printf("Database error : %s\n", message)
return DatabaseError{
message: message,
}
}
func (e *DatabaseError) Error() string {
return e.message
}
func (e *DatabaseError) HasError() bool {
return e.message != ""
}
type DatabaseResult struct {
Error DatabaseError
SqlRows *sql.Rows
SqlResult sql.Result
ParsedResult map[string][]sql.NullString
}
const (
profiler_DATABASE_CONNECT string = "Database Connect"
profiler_DATABASE_QUERY string = "Database Query"
@ -372,9 +398,8 @@ var databaseStructure databaseDefinition = databaseDefinition{
},
Command: `
DECLARE f_user_id INT(11);
DECLARE f_salt VARCHAR(32);
DECLARE f_password VARCHAR(256);
DECLARE f_user VARCHAR(256);
DECLARE f_salt VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
DECLARE f_password VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
SELECT TO_BASE64(RANDOM_BYTES(16)) INTO f_salt;
SELECT SHA2(CONCAT(LOWER(f_user_name), f_user_password, f_salt), 512) INTO f_password;
@ -383,6 +408,40 @@ INSERT INTO user(name, password, password_salt) VALUES (f_user_name, f_password,
SELECT LAST_INSERT_ID() INTO f_user_id;
RETURN f_user_id;`,
},
{
Name: "CheckUser",
Parameters: []storedRoutineParameterDefinition{
{
Name: "f_user_name",
Type: "VARCHAR(256)",
Charset: "utf8mb4",
Collation: "utf8mb4_unicode_ci",
},
{
Name: "f_user_password",
Type: "VARCHAR(256)",
Charset: "utf8mb4",
Collation: "utf8mb4_bin",
},
},
Return: storedRoutineParameterDefinition{
Type: "INT(11)",
},
Command: `
DECLARE f_user_id INT(11) DEFAULT NULL;
DECLARE f_salt VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
DECLARE f_password VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
DECLARE f_user VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
SET f_user = LOWER(f_user_name);
SELECT password_salt INTO f_salt FROM user WHERE name = f_user LIMIT 1;
SELECT SHA2(CONCAT(f_user, f_user_password, f_salt), 512) INTO f_password;
SELECT id INTO f_user_id FROM user WHERE name = f_user AND password = f_password LIMIT 1;
RETURN f_user_id;`,
},
},
@ -439,9 +498,11 @@ func connect() {
profiler.Add(profiler_DATABASE_CONNECT, time.Duration(time.Since(tStart).Microseconds()))
}
func ExecuteStoredRoutine(name string, args ...any) {
func ExecuteStoredRoutine(routineName string, args ...any) DatabaseResult {
if !isInit {
return
return DatabaseResult{
Error: createDatabaseError("Database not Init"),
}
}
if storedRoutines == nil {
fetchStoredRoutines()
@ -449,16 +510,76 @@ func ExecuteStoredRoutine(name string, args ...any) {
var exist bool
var routineType uint8
routineType, exist = storedRoutines[name]
routineType, exist = storedRoutines[routineName]
if !exist {
return
return DatabaseResult{
Error: createDatabaseError(fmt.Sprintf("Routine \"%s\" does not exist", routineName)),
}
}
var databaseResult DatabaseResult
switch routineType {
case database_ROUTINE_TYPE_PROCEDURE:
callStoredProcedure(name, args...)
databaseResult = callStoredProcedure(routineName, args...)
case database_ROUTINE_TYPE_FUNCTION:
callStoredFunction(name, args...)
databaseResult = callStoredFunction(routineName, args...)
default:
return DatabaseResult{
Error: createDatabaseError(fmt.Sprintf("Routine \"%s\" isn't a FUNCTION nor a PROCEDURE", routineName)),
}
}
return databaseResult
}
func DecodeDatabaseResult(databaseResult *DatabaseResult) {
var err error
var rows *sql.Rows = databaseResult.SqlRows
if rows != nil {
defer func() {
databaseResult.SqlRows.Close()
databaseResult.SqlRows = nil
}()
var columns []string
if columns, err = rows.Columns(); err != nil {
databaseResult.Error = createDatabaseError(fmt.Sprintf("Error reading columns name : %s", err))
rows.Close()
databaseResult.SqlRows = nil
return
}
if rows.Err() != nil {
fmt.Printf("Error : %s\n", rows.Err())
}
databaseResult.ParsedResult = map[string][]sql.NullString{}
var numCol int = len(columns)
for _, c := range columns {
databaseResult.ParsedResult[c] = make([]sql.NullString, 0)
}
for rows.Next() {
var args []any = make([]any, numCol)
var strArr []sql.NullString = make([]sql.NullString, numCol)
for i := range numCol {
args[i] = &strArr[i]
}
if err = rows.Scan(args...); err != nil {
databaseResult.Error = createDatabaseError(fmt.Sprintf("Error Scanning database response : %s", err))
break
}
for i, c := range columns {
databaseResult.ParsedResult[c] = append(databaseResult.ParsedResult[c], strArr[i])
}
}
if err = rows.Err(); err != nil {
databaseResult.Error = createDatabaseError(fmt.Sprintf("Error Scanning database response : %s", err))
}
}
}
@ -481,9 +602,11 @@ func ping() {
}
}
func executeQuery(query string, args ...any) *sql.Rows {
func executeQuery(query string, args ...any) DatabaseResult {
if !isInit {
return nil
return DatabaseResult{
Error: createDatabaseError("Database not Init"),
}
}
if db == nil {
connect()
@ -497,6 +620,7 @@ func executeQuery(query string, args ...any) *sql.Rows {
if err == nil {
break
}
log.Printf("Error while querying the database [%d/%d] : %s\n", i+1, dbConfig.RetriesOnError, err)
if i+1 == dbConfig.RetriesOnError {
break
@ -508,15 +632,22 @@ func executeQuery(query string, args ...any) *sql.Rows {
if err != nil {
log.Printf("Cannot query the database ! Query : %s", query)
log.Fatalln("With args :", args)
log.Printf("With args : %s", args...)
return DatabaseResult{
Error: createDatabaseError("Database Query Failed"),
}
}
return rows
return DatabaseResult{
SqlRows: rows,
}
}
func executeExec(query string, args ...any) sql.Result {
func executeExec(query string, args ...any) DatabaseResult {
if !isInit {
return nil
return DatabaseResult{
Error: createDatabaseError("Database not Init"),
}
}
if db == nil {
connect()
@ -541,15 +672,21 @@ func executeExec(query string, args ...any) sql.Result {
if err != nil {
log.Printf("Cannot execute on the database ! Command : %s", query)
log.Fatalln("With args : ", args)
log.Printf("With args : %s\n", args)
return DatabaseResult{
Error: createDatabaseError("Database Exec Failed"),
}
}
return result
return DatabaseResult{
SqlResult: result,
}
}
func fetchStoredRoutines() {
storedRoutines = nil
var rows *sql.Rows = executeQuery("SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA=?;", dbConfig.Database)
var dbResult DatabaseResult = executeQuery("SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA=?;", dbConfig.Database)
var rows *sql.Rows = dbResult.SqlRows
if rows != nil {
storedRoutines = make(map[string]uint8, 0)
defer rows.Close()
@ -568,7 +705,7 @@ func fetchStoredRoutines() {
fmt.Println(storedRoutines)
}
func callStoredProcedure(name string, args ...any) {
func callStoredProcedure(name string, args ...any) DatabaseResult {
var query string
if len(args) == 0 {
query = fmt.Sprintf("CALL %s()", name)
@ -579,39 +716,28 @@ func callStoredProcedure(name string, args ...any) {
query = fmt.Sprintf("CALL %s(?%s)", name, query)
}
var rows *sql.Rows = executeQuery(query, args...)
defer rows.Close()
for rows.Next() {
var res string
rows.Scan(&res)
log.Println(res)
}
var dbResult DatabaseResult = executeQuery(query, args...)
return dbResult
}
func callStoredFunction(name string, args ...any) {
func callStoredFunction(name string, args ...any) DatabaseResult {
var query string
if len(args) == 0 {
query = fmt.Sprintf("SELECT %s()", name)
query = fmt.Sprintf("SELECT %s() AS result", name)
} else if len(args) == 1 {
query = fmt.Sprintf("SELECT %s(?)", name)
query = fmt.Sprintf("SELECT %s(?) AS result", name)
} else {
query = strings.Repeat(", ?", len(args)-1)
query = fmt.Sprintf("SELECT %s(?%s)", name, query)
query = fmt.Sprintf("SELECT %s(?%s) AS result", name, query)
}
var rows *sql.Rows = executeQuery(query, args...)
defer rows.Close()
for rows.Next() {
var res string
rows.Scan(&res)
log.Println(res)
}
var dbResult DatabaseResult = executeQuery(query, args...)
return dbResult
}
func fetchTables() []string {
var rows *sql.Rows = executeQuery("SHOW TABLES;")
var queryResult DatabaseResult = executeQuery("SHOW TABLES;")
var rows *sql.Rows = queryResult.SqlRows
//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 {
@ -626,7 +752,8 @@ func fetchTables() []string {
}
func fetchColumns(table string) []columsDefinition {
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 queryResult DatabaseResult = 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 rows *sql.Rows = queryResult.SqlRows
var columns []columsDefinition = make([]columsDefinition, 0)
if rows != nil {
defer rows.Close()
@ -654,7 +781,8 @@ func fetchIndexes() []indexDefinition {
// Add PRIMARY : ALTER TABLE <table> ADD CONSTRAINT PRIMARY KEY(<columns>);
// Add UNIQUE : ALTER TABLE <table> ADD CONSTRAINT <constraint_name> UNIQUE(<columns>);
var rows *sql.Rows = executeQuery("SELECT S.COLUMN_NAME as COLUMN_NAME, S.INDEX_NAME AS INDEX_NAME, TC.CONSTRAINT_TYPE AS CONSTRAINT_TYPE, S.TABLE_NAME AS TABLE_NAME FROM information_schema.STATISTICS as S INNER JOIN information_schema.TABLE_CONSTRAINTS as TC ON TC.TABLE_SCHEMA = S.TABLE_SCHEMA AND TC.CONSTRAINT_NAME = S.INDEX_NAME AND TC.TABLE_NAME = S.TABLE_NAME WHERE S.TABLE_SCHEMA = ? AND TC.CONSTRAINT_TYPE IN(\"UNIQUE\", \"PRIMARY KEY\");", dbConfig.Database)
var queryResult DatabaseResult = executeQuery("SELECT S.COLUMN_NAME as COLUMN_NAME, S.INDEX_NAME AS INDEX_NAME, TC.CONSTRAINT_TYPE AS CONSTRAINT_TYPE, S.TABLE_NAME AS TABLE_NAME FROM information_schema.STATISTICS as S INNER JOIN information_schema.TABLE_CONSTRAINTS as TC ON TC.TABLE_SCHEMA = S.TABLE_SCHEMA AND TC.CONSTRAINT_NAME = S.INDEX_NAME AND TC.TABLE_NAME = S.TABLE_NAME WHERE S.TABLE_SCHEMA = ? AND TC.CONSTRAINT_TYPE IN(\"UNIQUE\", \"PRIMARY KEY\");", dbConfig.Database)
var rows *sql.Rows = queryResult.SqlRows
var indexes []indexDefinition = make([]indexDefinition, 0)
if rows != nil {
defer rows.Close()
@ -681,7 +809,8 @@ func fetchForeignKeys() []foreignKeyDefinition {
// ADD ALTER TABLE <table> ADD CONSTRAINT <constraint_name> FOREIGN KEY(<column>) REFERENCES <ref_table>(<ref_column>) ON DELETE <CASCADE / SET NULL> UPDATE <CASCADE / SET NULL>;
// SELECT RC.CONSTRAINT_NAME as CONTRAINT_NAME, KCU.TABLE_NAME, KCU.COLUMN_NAME, KCU.REFERENCED_TABLE_NAME, KCU.REFERENCED_COLUMN_NAME, RC.UPDATE_RULE, RC.DELETE_RULE FROM REFERENTIAL_CONSTRAINTS AS RC INNER JOIN KEY_COLUMN_USAGE AS KCU ON KCU.CONSTRAINT_NAME = RC.CONSTRAINT_NAME WHERE KCU.TABLE_SCHEMA = "RPG";
var rows *sql.Rows = executeQuery("SELECT RC.CONSTRAINT_NAME as CONTRAINT_NAME, KCU.TABLE_NAME, KCU.COLUMN_NAME, KCU.REFERENCED_TABLE_NAME, KCU.REFERENCED_COLUMN_NAME, RC.UPDATE_RULE, RC.DELETE_RULE FROM information_schema.REFERENTIAL_CONSTRAINTS AS RC INNER JOIN information_schema.KEY_COLUMN_USAGE AS KCU ON KCU.CONSTRAINT_NAME = RC.CONSTRAINT_NAME WHERE KCU.TABLE_SCHEMA = ?;", dbConfig.Database)
var queryResult DatabaseResult = executeQuery("SELECT RC.CONSTRAINT_NAME as CONTRAINT_NAME, KCU.TABLE_NAME, KCU.COLUMN_NAME, KCU.REFERENCED_TABLE_NAME, KCU.REFERENCED_COLUMN_NAME, RC.UPDATE_RULE, RC.DELETE_RULE FROM information_schema.REFERENTIAL_CONSTRAINTS AS RC INNER JOIN information_schema.KEY_COLUMN_USAGE AS KCU ON KCU.CONSTRAINT_NAME = RC.CONSTRAINT_NAME WHERE KCU.TABLE_SCHEMA = ?;", dbConfig.Database)
var rows *sql.Rows = queryResult.SqlRows
var foreignKeys []foreignKeyDefinition = make([]foreignKeyDefinition, 0)
if rows != nil {
defer rows.Close()