feat(pgsql): enhance SQL statement execution logging

* add context information for executed SQL statements
* implement extractStatementContext function for context retrieval
This commit is contained in:
2026-05-19 19:42:57 +02:00
parent bb7ceb37fe
commit d9f27c1775
+169
View File
@@ -1593,7 +1593,12 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro
} }
stmtType := detectStatementType(stmtTrimmed) stmtType := detectStatementType(stmtTrimmed)
stmtCtx := extractStatementContext(stmtTrimmed)
if stmtCtx != "" {
fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s] %s...\n", i+1, len(statements), stmtType, stmtCtx)
} else {
fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s]...\n", i+1, len(statements), stmtType) fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s]...\n", i+1, len(statements), stmtType)
}
_, execErr := conn.Exec(ctx, stmt) _, execErr := conn.Exec(ctx, stmt)
if execErr != nil { if execErr != nil {
@@ -1728,6 +1733,170 @@ func getCurrentTimestamp() string {
return time.Now().Format("2006-01-02 15:04:05") return time.Now().Format("2006-01-02 15:04:05")
} }
// extractStatementContext returns a human-readable schema/table/column context string for a SQL statement.
func extractStatementContext(stmt string) string {
upper := strings.ToUpper(stmt)
// DO $$ blocks: extract identifiers from information_schema WHERE clauses
if strings.HasPrefix(upper, "DO $$") || strings.HasPrefix(upper, "DO $") {
schema := extractSQLStringValue(stmt, "table_schema")
table := extractSQLStringValue(stmt, "table_name")
column := extractSQLStringValue(stmt, "column_name")
constraint := extractSQLStringValue(stmt, "constraint_name")
return buildStmtContext(schema, table, column, constraint)
}
// ALTER TABLE [schema.]table ...
if strings.HasPrefix(upper, "ALTER TABLE") {
schema, table := parseQualifiedIdent(strings.TrimSpace(stmt[11:]))
if strings.Contains(upper, "ADD COLUMN") {
col := firstIdentAfterKeyword(stmt, upper, "ADD COLUMN")
return buildStmtContext(schema, table, col, "")
}
if strings.Contains(upper, "ALTER COLUMN") {
col := firstIdentAfterKeyword(stmt, upper, "ALTER COLUMN")
return buildStmtContext(schema, table, col, "")
}
if strings.Contains(upper, "ADD CONSTRAINT") {
con := firstIdentAfterKeyword(stmt, upper, "ADD CONSTRAINT")
return buildStmtContext(schema, table, "", con)
}
if strings.Contains(upper, "DROP CONSTRAINT") {
con := firstIdentAfterKeyword(stmt, upper, "DROP CONSTRAINT")
return buildStmtContext(schema, table, "", con)
}
return buildStmtContext(schema, table, "", "")
}
// CREATE TABLE [IF NOT EXISTS] [schema.]table
if strings.HasPrefix(upper, "CREATE TABLE") {
rest := strings.TrimSpace(stmt[12:])
if strings.HasPrefix(strings.ToUpper(rest), "IF NOT EXISTS") {
rest = strings.TrimSpace(rest[13:])
}
schema, table := parseQualifiedIdent(rest)
return buildStmtContext(schema, table, "", "")
}
// CREATE SCHEMA name
if strings.HasPrefix(upper, "CREATE SCHEMA") {
name := firstBareIdent(strings.TrimSpace(stmt[13:]))
return name
}
// CREATE [UNIQUE] INDEX ... ON [schema.]table
if strings.HasPrefix(upper, "CREATE INDEX") || strings.HasPrefix(upper, "CREATE UNIQUE INDEX") {
onIdx := strings.Index(upper, " ON ")
if onIdx != -1 {
schema, table := parseQualifiedIdent(strings.TrimSpace(stmt[onIdx+4:]))
return buildStmtContext(schema, table, "", "")
}
}
// COMMENT ON TABLE [schema.]table
if strings.HasPrefix(upper, "COMMENT ON TABLE") {
schema, table := parseQualifiedIdent(strings.TrimSpace(stmt[16:]))
return buildStmtContext(schema, table, "", "")
}
// COMMENT ON COLUMN [schema.]table.col
if strings.HasPrefix(upper, "COMMENT ON COLUMN") {
return firstBareIdent(strings.TrimSpace(stmt[17:]))
}
return ""
}
// extractSQLStringValue extracts 'value' from patterns like: key = 'value' (case-insensitive key match).
func extractSQLStringValue(stmt, key string) string {
lower := strings.ToLower(stmt)
idx := strings.Index(lower, strings.ToLower(key))
if idx == -1 {
return ""
}
rest := strings.TrimSpace(stmt[idx+len(key):])
eqIdx := strings.IndexByte(rest, '=')
if eqIdx == -1 || eqIdx > 5 {
return ""
}
rest = strings.TrimSpace(rest[eqIdx+1:])
if len(rest) == 0 || rest[0] != '\'' {
return ""
}
rest = rest[1:]
end := strings.IndexByte(rest, '\'')
if end == -1 {
return ""
}
return rest[:end]
}
// parseQualifiedIdent extracts (schema, name) from the start of s, handling optional "schema"."table" quoting.
func parseQualifiedIdent(s string) (schema, name string) {
token := firstBareIdent(s)
parts := strings.SplitN(token, ".", 2)
if len(parts) == 2 {
return stripQuotes(parts[0]), stripQuotes(parts[1])
}
return "", stripQuotes(token)
}
// firstBareIdent returns the first whitespace/delimiter-terminated token, stripping outer quotes.
func firstBareIdent(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return ""
}
end := strings.IndexAny(s, " \t\n(,;")
if end == -1 {
return s
}
return s[:end]
}
// firstIdentAfterKeyword returns the first identifier token after keyword (matched case-insensitively via upperStmt).
func firstIdentAfterKeyword(stmt, upperStmt, keyword string) string {
idx := strings.Index(upperStmt, keyword)
if idx == -1 {
return ""
}
return stripQuotes(firstBareIdent(strings.TrimSpace(stmt[idx+len(keyword):])))
}
// stripQuotes removes surrounding double-quotes from an identifier.
func stripQuotes(s string) string {
return strings.Trim(s, "\"")
}
// buildStmtContext assembles a display string from available identifiers.
func buildStmtContext(schema, table, column, constraint string) string {
var b strings.Builder
if schema != "" && table != "" {
b.WriteString(schema)
b.WriteByte('.')
b.WriteString(table)
} else if table != "" {
b.WriteString(table)
}
if column != "" {
if b.Len() > 0 {
b.WriteByte(' ')
}
b.WriteByte('(')
b.WriteString(column)
b.WriteByte(')')
}
if constraint != "" {
if b.Len() > 0 {
b.WriteByte(' ')
}
b.WriteByte('[')
b.WriteString(constraint)
b.WriteByte(']')
}
return b.String()
}
// detectStatementType detects the type of SQL statement for logging // detectStatementType detects the type of SQL statement for logging
func detectStatementType(stmt string) string { func detectStatementType(stmt string) string {
upperStmt := strings.ToUpper(stmt) upperStmt := strings.ToUpper(stmt)