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:
+170
-1
@@ -1593,7 +1593,12 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro
|
||||
}
|
||||
|
||||
stmtType := detectStatementType(stmtTrimmed)
|
||||
fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s]...\n", i+1, len(statements), stmtType)
|
||||
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)
|
||||
}
|
||||
|
||||
_, execErr := conn.Exec(ctx, stmt)
|
||||
if execErr != nil {
|
||||
@@ -1728,6 +1733,170 @@ func getCurrentTimestamp() string {
|
||||
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
|
||||
func detectStatementType(stmt string) string {
|
||||
upperStmt := strings.ToUpper(stmt)
|
||||
|
||||
Reference in New Issue
Block a user