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:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user