Skip to content

Commit bc56f4b

Browse files
committed
feat: Add support for Functions and Stored Procedures (Fixes #2)
1 parent ddba66b commit bc56f4b

5 files changed

Lines changed: 125 additions & 2 deletions

File tree

internal/db/queries.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,3 +388,31 @@ func getMaterializedViews(ctx context.Context, db *DB, schemaName string) ([]typ
388388
}
389389
return matViews, rows.Err()
390390
}
391+
392+
func getFunctions(ctx context.Context, db *DB, schemaName string) ([]types.Function, error) {
393+
// Query to extract functions, handling overloading correctly
394+
query := `
395+
SELECT
396+
p.proname as name,
397+
pg_get_function_identity_arguments(p.oid) as arguments,
398+
pg_get_functiondef(p.oid) as definition
399+
FROM pg_proc p
400+
JOIN pg_namespace n ON p.pronamespace = n.oid
401+
WHERE n.nspname = $1
402+
`
403+
rows, err := db.Query(ctx, query, schemaName)
404+
if err != nil {
405+
return nil, err
406+
}
407+
defer rows.Close()
408+
409+
var functions []types.Function
410+
for rows.Next() {
411+
var f types.Function
412+
if err := rows.Scan(&f.Name, &f.Arguments, &f.Definition); err != nil {
413+
return nil, err
414+
}
415+
functions = append(functions, f)
416+
}
417+
return functions, rows.Err()
418+
}

internal/db/schema.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func (i *Introspector) Introspect(ctx context.Context) (*types.Schema, error) {
3434
Types: make([]types.Type, 0),
3535
Views: make([]types.View, 0),
3636
MaterializedViews: make([]types.MaterializedView, 0),
37+
Functions: make([]types.Function, 0),
3738
}
3839

3940
// Get tables
@@ -127,9 +128,19 @@ func (i *Introspector) Introspect(ctx context.Context) (*types.Schema, error) {
127128
}
128129
schema.MaterializedViews = matViews
129130

131+
// Get functions
130132
if i.verbose {
131-
log.Printf("Introspection complete: %d tables, %d sequences, %d types, %d views, %d materialized views",
132-
len(schema.Tables), len(schema.Sequences), len(schema.Types), len(schema.Views), len(schema.MaterializedViews))
133+
log.Println("Introspecting functions...")
134+
}
135+
functions, err := getFunctions(ctx, i.db, i.schemaName)
136+
if err != nil {
137+
return nil, fmt.Errorf("failed to get functions: %w", err)
138+
}
139+
schema.Functions = functions
140+
141+
if i.verbose {
142+
log.Printf("Introspection complete: %d tables, %d sequences, %d types, %d views, %d materialized views, %d functions",
143+
len(schema.Tables), len(schema.Sequences), len(schema.Types), len(schema.Views), len(schema.MaterializedViews), len(schema.Functions))
133144
}
134145

135146
return schema, nil

internal/diff/engine.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package diff
22

33
import (
4+
"fmt"
45
"strconv"
56
"strings"
67

@@ -40,6 +41,9 @@ func (e *DiffEngine) Compare() types.DiffList {
4041
// Materialized View comparison
4142
differences = append(differences, e.compareMaterializedViews()...)
4243

44+
// Function comparison
45+
differences = append(differences, e.compareFunctions()...)
46+
4347
return differences
4448
}
4549

@@ -725,3 +729,55 @@ func (e *DiffEngine) compareMaterializedViews() types.DiffList {
725729

726730
return differences
727731
}
732+
733+
func (e *DiffEngine) compareFunctions() types.DiffList {
734+
var differences types.DiffList
735+
736+
sourceFuncs := make(map[string]*types.Function)
737+
targetFuncs := make(map[string]*types.Function)
738+
739+
for i := range e.source.Functions {
740+
f := &e.source.Functions[i]
741+
key := fmt.Sprintf("%s(%s)", f.Name, f.Arguments)
742+
sourceFuncs[key] = f
743+
}
744+
for i := range e.target.Functions {
745+
f := &e.target.Functions[i]
746+
key := fmt.Sprintf("%s(%s)", f.Name, f.Arguments)
747+
targetFuncs[key] = f
748+
}
749+
750+
// New and altered functions
751+
for key, sourceFunc := range sourceFuncs {
752+
if targetFunc, exists := targetFuncs[key]; !exists {
753+
differences = append(differences, types.Diff{
754+
Type: types.DiffAdd,
755+
Object: types.ObjectFunction,
756+
Name: key, // key includes func_name(args)
757+
NewValue: sourceFunc.Definition, // complete CREATE OR REPLACE snippet
758+
})
759+
} else if sourceFunc.Definition != targetFunc.Definition {
760+
// Definition changed
761+
differences = append(differences, types.Diff{
762+
Type: types.DiffAlter,
763+
Object: types.ObjectFunction,
764+
Name: key,
765+
OldValue: targetFunc.Definition,
766+
NewValue: sourceFunc.Definition,
767+
})
768+
}
769+
}
770+
771+
// Dropped functions
772+
for key := range targetFuncs {
773+
if _, exists := sourceFuncs[key]; !exists {
774+
differences = append(differences, types.Diff{
775+
Type: types.DiffDrop,
776+
Object: types.ObjectFunction,
777+
Name: key,
778+
})
779+
}
780+
}
781+
782+
return differences
783+
}

internal/diff/generator.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,25 @@ func (g *SQLGenerator) generateMaterializedViewDiff(diff *types.Diff) string {
473473
}
474474
}
475475

476+
func (g *SQLGenerator) generateFunctionDiff(diff *types.Diff) string {
477+
schema := schemaPrefix(g.schemaName)
478+
definition := strings.TrimRight(diff.NewValue, " \t\r\n;")
479+
// Notice: diff.Name contains func_name(args) to handle overloads specifically for drop
480+
switch diff.Type {
481+
case types.DiffAdd:
482+
return fmt.Sprintf("-- Create function: %s\n%s;",
483+
diff.Name, definition)
484+
case types.DiffAlter:
485+
return fmt.Sprintf("-- Alter function: %s\n%s;",
486+
diff.Name, definition)
487+
case types.DiffDrop:
488+
return fmt.Sprintf("-- Drop function: %s\nDROP FUNCTION IF EXISTS %s%s CASCADE;",
489+
diff.Name, schema, diff.Name)
490+
default:
491+
return ""
492+
}
493+
}
494+
476495
// GenerateSQL is a legacy function
477496
func GenerateSQL(diffs types.DiffList, schemaName string) string {
478497
gen := NewSQLGenerator(diffs, schemaName)

pkg/types/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type Schema struct {
1010
Types []Type `json:"types,omitempty"`
1111
Views []View `json:"views,omitempty"`
1212
MaterializedViews []MaterializedView `json:"materialized_views,omitempty"`
13+
Functions []Function `json:"functions,omitempty"`
1314
}
1415

1516
type Table struct {
@@ -81,6 +82,13 @@ type MaterializedView struct {
8182
Definition string `json:"definition"`
8283
}
8384

85+
// Function represents a PostgreSQL stored procedure or function
86+
type Function struct {
87+
Name string `json:"name"`
88+
Arguments string `json:"arguments"`
89+
Definition string `json:"definition"`
90+
}
91+
8492
type DiffType string
8593

8694
const (
@@ -102,6 +110,7 @@ const (
102110
ObjectType DiffObject = "TYPE"
103111
ObjectView DiffObject = "VIEW"
104112
ObjectMaterializedView DiffObject = "MATERIALIZED_VIEW"
113+
ObjectFunction DiffObject = "FUNCTION"
105114
)
106115

107116
type Diff struct {

0 commit comments

Comments
 (0)