diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 7f9c404..54a031a 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -26,6 +26,8 @@ type Schema struct { Name string `json:"name"` // Tables is a map of virtual table name -> table mapping Tables map[string]Table `json:"tables"` + // Views is a map of virtual view name -> view mapping + Views map[string]View `json:"views"` } type Table struct { @@ -57,6 +59,23 @@ type Table struct { UniqueConstraints map[string]UniqueConstraint `json:"uniqueConstraints"` } +type View struct { + // OID for the view + OID string `json:"oid"` + + // Name is the actual name in postgres + Name string `json:"name"` + + // Optional comment for the view + Comment string `json:"comment"` + + // Definition is the SQL definition of the view + Definition string `json:"definition"` + + // Columns is a map of virtual column name -> column mapping + Columns map[string]Column `json:"columns"` +} + type Column struct { // Name is the actual name in postgres Name string `json:"name"` diff --git a/pkg/state/state.go b/pkg/state/state.go index 825d95b..b60363c 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -94,7 +94,7 @@ STABLE; CREATE OR REPLACE FUNCTION %[1]s.read_schema(schemaname text) RETURNS jsonb LANGUAGE plpgsql AS $$ DECLARE - tables jsonb; + result jsonb; BEGIN SELECT json_build_object( 'name', schemaname, @@ -255,11 +255,43 @@ BEGIN WHERE ns.nspname = schemaname AND t.relkind IN ('r', 'p') -- tables only (ignores views, materialized views & foreign tables) + ), + 'views', ( + SELECT COALESCE(json_object_agg(v.relname, jsonb_build_object( + 'name', v.relname, + 'oid', v.oid, + 'comment', descr.description, + 'definition', pg_get_viewdef(v.oid, true), + 'columns', ( + SELECT COALESCE(json_object_agg(name, c), '{}'::json) FROM ( + SELECT + attr.attname AS name, + format_type(attr.atttypid, attr.atttypmod) AS type, + descr.description AS comment + FROM + pg_attribute AS attr + LEFT JOIN pg_description AS descr ON attr.attrelid = descr.objoid + AND attr.attnum = descr.objsubid + WHERE + attr.attnum > 0 + AND NOT attr.attisdropped + AND attr.attrelid = v.oid + ORDER BY + attr.attnum + ) c + ) + )), '{}'::json) FROM pg_class AS v + INNER JOIN pg_namespace AS ns ON v.relnamespace = ns.oid + LEFT JOIN pg_description AS descr ON v.oid = descr.objoid + AND descr.objsubid = 0 + WHERE + ns.nspname = schemaname + AND v.relkind = 'v' -- views only ) ) - INTO tables; + INTO result; - RETURN tables; + RETURN result; END; $$; diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 1fe04ab..fb3596c 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -311,6 +311,7 @@ func TestReadSchema(t *testing.T) { wantSchema: &schema.Schema{ Name: "public", Tables: map[string]schema.Table{}, + Views: map[string]schema.View{}, }, }, { @@ -329,6 +330,27 @@ func TestReadSchema(t *testing.T) { ForeignKeys: map[string]schema.ForeignKey{}, }, }, + Views: map[string]schema.View{}, + }, + }, + { + name: "one view without columns", + createStmt: "CREATE VIEW public.view1 AS SELECT 1 AS foo", + wantSchema: &schema.Schema{ + Name: "public", + Tables: map[string]schema.Table{}, + Views: map[string]schema.View{ + "view1": { + Name: "view1", + Definition: " SELECT 1 AS foo;", + Columns: map[string]schema.Column{ + "foo": { + Name: "foo", + Type: "integer", + }, + }, + }, + }, }, }, { @@ -353,6 +375,7 @@ func TestReadSchema(t *testing.T) { ForeignKeys: map[string]schema.ForeignKey{}, }, }, + Views: map[string]schema.View{}, }, }, { @@ -389,6 +412,7 @@ func TestReadSchema(t *testing.T) { ForeignKeys: map[string]schema.ForeignKey{}, }, }, + Views: map[string]schema.View{}, }, }, { @@ -424,6 +448,7 @@ func TestReadSchema(t *testing.T) { ForeignKeys: map[string]schema.ForeignKey{}, }, }, + Views: map[string]schema.View{}, }, }, { @@ -478,6 +503,7 @@ func TestReadSchema(t *testing.T) { UniqueConstraints: map[string]schema.UniqueConstraint{}, }, }, + Views: map[string]schema.View{}, }, }, { @@ -532,6 +558,7 @@ func TestReadSchema(t *testing.T) { UniqueConstraints: map[string]schema.UniqueConstraint{}, }, }, + Views: map[string]schema.View{}, }, }, { @@ -574,6 +601,7 @@ func TestReadSchema(t *testing.T) { UniqueConstraints: map[string]schema.UniqueConstraint{}, }, }, + Views: map[string]schema.View{}, }, }, { @@ -621,6 +649,7 @@ func TestReadSchema(t *testing.T) { }, }, }, + Views: map[string]schema.View{}, }, }, { @@ -668,6 +697,7 @@ func TestReadSchema(t *testing.T) { }, }, }, + Views: map[string]schema.View{}, }, }, { @@ -703,6 +733,7 @@ func TestReadSchema(t *testing.T) { UniqueConstraints: map[string]schema.UniqueConstraint{}, }, }, + Views: map[string]schema.View{}, }, }, { @@ -727,6 +758,7 @@ func TestReadSchema(t *testing.T) { UniqueConstraints: map[string]schema.UniqueConstraint{}, }, }, + Views: map[string]schema.View{}, }, }, } @@ -745,7 +777,7 @@ func TestReadSchema(t *testing.T) { if err != nil { t.Fatal(err) } - if diff := cmp.Diff(tt.wantSchema, gotSchema, cmpopts.IgnoreFields(schema.Table{}, "OID")); diff != "" { + if diff := cmp.Diff(tt.wantSchema, gotSchema, cmpopts.IgnoreFields(schema.Table{}, "OID"), cmpopts.IgnoreFields(schema.View{}, "OID")); diff != "" { t.Errorf("expected schema mismatch (-want +got):\n%s", diff) } })