Skip to content

Commit 4bfe39e

Browse files
committed
Added support for setting and retrieving comments on tables and columns via the DDL Admin API
1 parent edd09e6 commit 4bfe39e

4 files changed

Lines changed: 134 additions & 9 deletions

File tree

database/columns.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type Column struct {
77
Type string `json:"type"`
88
NotNull bool `json:"notnull"`
99
Default *string `json:"default"`
10+
Comment *string `json:"comment"`
1011
Constraints []string `json:"constraints"`
1112
Table string `json:"table,omitempty"`
1213
Schema string `json:"schema,omitempty"`
@@ -17,12 +18,17 @@ type ColumnUpdate struct {
1718
Type *string `json:"type"`
1819
NotNull *bool `json:"notnull"`
1920
Default *string `json:"default"`
21+
Comment *string `json:"comment"`
2022
}
2123

2224
const columnsQuery = `
23-
SELECT column_name, udt_name, is_nullable, column_default, table_name, table_schema
24-
FROM information_schema.columns
25-
WHERE table_name = $1 AND table_schema = $2`
25+
SELECT c.column_name, c.udt_name, c.is_nullable, c.column_default,
26+
col_description(cl.oid, c.ordinal_position),
27+
c.table_name, c.table_schema
28+
FROM information_schema.columns c
29+
JOIN pg_class cl ON cl.relname = c.table_name
30+
JOIN pg_namespace n ON n.oid = cl.relnamespace AND n.nspname = c.table_schema
31+
WHERE c.table_name = $1 AND c.table_schema = $2`
2632

2733
func GetColumns(ctx context.Context, tablename string) ([]Column, error) {
2834
conn, schemaname := GetConnAndSchema(ctx)
@@ -41,7 +47,7 @@ func GetColumns(ctx context.Context, tablename string) ([]Column, error) {
4147
var nullable string
4248
column := Column{}
4349
for rows.Next() {
44-
err := rows.Scan(&column.Name, &column.Type, &nullable, &column.Default, &column.Table, &column.Schema)
50+
err := rows.Scan(&column.Name, &column.Type, &nullable, &column.Default, &column.Comment, &column.Table, &column.Schema)
4551
if err != nil {
4652
return columns, err
4753
}
@@ -65,7 +71,7 @@ func GetColumn(ctx context.Context, tablename string, name string) (*Column, err
6571
column := &Column{}
6672
var nullable string
6773
err = conn.QueryRow(ctx, columnsQuery, tablename, schemaname).
68-
Scan(&column.Name, &column.Type, &nullable, &column.Default, &column.Table, &column.Schema)
74+
Scan(&column.Name, &column.Type, &nullable, &column.Default, &column.Comment, &column.Table, &column.Schema)
6975
if err != nil {
7076
return nil, err
7177
}
@@ -84,6 +90,14 @@ func CreateColumn(ctx context.Context, column *Column) (*Column, error) {
8490
if err != nil {
8591
return nil, err
8692
}
93+
// COMMENT
94+
if column.Comment != nil {
95+
comment := "COMMENT ON COLUMN " + ftablename + "." + quote(column.Name) + " IS " + quoteLit(*column.Comment)
96+
_, err = conn.Exec(ctx, comment)
97+
if err != nil {
98+
return nil, err
99+
}
100+
}
87101
//db.refreshTable(ctx, column.Table)
88102
return column, nil
89103
}
@@ -135,6 +149,14 @@ func UpdateColumn(ctx context.Context, tablename string, name string, column *Co
135149
return err
136150
}
137151
}
152+
// COMMENT
153+
if column.Comment != nil {
154+
comment := "COMMENT ON COLUMN " + ftablename + "." + quote(name) + " IS " + quoteLit(*column.Comment)
155+
_, err = tx.Exec(ctx, comment)
156+
if err != nil {
157+
return err
158+
}
159+
}
138160
// NAME as the last update
139161
if column.Name != nil && *column.Name != name {
140162
alter = "ALTER TABLE " + ftablename + " RENAME " + name + " TO " + *column.Name

database/tables.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type Table struct {
88
Name string `json:"name"`
99
Schema string `json:"schema"`
1010
Owner string `json:"owner"`
11+
Comment *string `json:"comment"`
1112
RowSecurity bool `json:"rowsecurity"`
1213
Columns []Column `json:"columns,omitempty"`
1314
Constraints []string `json:"constraints"`
@@ -22,13 +23,15 @@ type TableUpdate struct {
2223
Name *string `json:"name"`
2324
Schema *string `json:"schema"`
2425
Owner *string `json:"owner"`
26+
Comment *string `json:"comment"`
2527
RowSecurity *bool `json:"rowsecurity"`
2628
}
2729

2830
const tablesQuery = `
2931
SELECT c.relname tablename,
3032
n.nspname schema,
3133
pg_get_userbyid(c.relowner) tableowner,
34+
obj_description(c.oid) comment,
3235
c.relrowsecurity rowsecurity,
3336
c.relhasindex hasindexes,
3437
c.relhastriggers hastriggers,
@@ -53,7 +56,7 @@ func GetTables(ctx context.Context) ([]Table, error) {
5356

5457
table := Table{}
5558
for rows.Next() {
56-
err := rows.Scan(&table.Name, &table.Schema, &table.Owner, &table.RowSecurity,
59+
err := rows.Scan(&table.Name, &table.Schema, &table.Owner, &table.Comment, &table.RowSecurity,
5760
&table.HasIndexes, &table.HasTriggers, &table.IsPartition)
5861
if err != nil {
5962
return tables, err
@@ -78,7 +81,7 @@ func GetTable(ctx context.Context, name string) (*Table, error) {
7881
table := Table{}
7982
err = conn.QueryRow(ctx,
8083
tablesQuery+" AND c.relname = $1 AND n.nspname = $2", name, schemaname).
81-
Scan(&table.Name, &table.Schema, &table.Owner, &table.RowSecurity, &table.HasIndexes, &table.HasTriggers, &table.IsPartition)
84+
Scan(&table.Name, &table.Schema, &table.Owner, &table.Comment, &table.RowSecurity, &table.HasIndexes, &table.HasTriggers, &table.IsPartition)
8285
if err != nil {
8386
return nil, err
8487
}
@@ -166,6 +169,24 @@ func CreateTable(ctx context.Context, table *Table) (*Table, error) {
166169
if err != nil {
167170
return nil, err
168171
}
172+
// COMMENT
173+
if table.Comment != nil {
174+
comment := "COMMENT ON TABLE " + ftablename + " IS " + quoteLit(*table.Comment)
175+
_, err = tx.Exec(ctx, comment)
176+
if err != nil {
177+
return nil, err
178+
}
179+
}
180+
// COLUMN COMMENTS
181+
for _, col := range table.Columns {
182+
if col.Comment != nil {
183+
comment := "COMMENT ON COLUMN " + ftablename + "." + quote(col.Name) + " IS " + quoteLit(*col.Comment)
184+
_, err = tx.Exec(ctx, comment)
185+
if err != nil {
186+
return nil, err
187+
}
188+
}
189+
}
169190

170191
err = tx.Commit(ctx)
171192
if err != nil {
@@ -209,6 +230,14 @@ func UpdateTable(ctx context.Context, name string, table *TableUpdate) error {
209230
return err
210231
}
211232
}
233+
// COMMENT
234+
if table.Comment != nil {
235+
comment := "COMMENT ON TABLE " + _sq(name, schemaname) + " IS " + quoteLit(*table.Comment)
236+
_, err = tx.Exec(ctx, comment)
237+
if err != nil {
238+
return err
239+
}
240+
}
212241
// SCHEMA
213242
if table.Schema != nil {
214243
alter = prefix + " SET SCHEMA " + quote(*table.Schema)

test/api/comments_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package test_api
2+
3+
import (
4+
"testing"
5+
6+
"github.com/sted/smoothdb/test"
7+
)
8+
9+
// Tests for issue #17: Add and read object comments using DDL admin API
10+
11+
func TestComments(t *testing.T) {
12+
13+
cmdConfig := test.Config{
14+
BaseUrl: "http://localhost:8082/admin/databases",
15+
CommonHeaders: test.Headers{"Authorization": {adminToken}},
16+
}
17+
18+
commands := []test.Command{
19+
{
20+
Method: "POST",
21+
Query: "/dbtest/tables",
22+
Body: `{
23+
"name": "comments_test",
24+
"comment": "This is a test table",
25+
"columns": [
26+
{"name": "id", "type": "int4", "notnull": true},
27+
{"name": "name", "type": "text", "comment": "The name field"}
28+
],
29+
"ifnotexists": true
30+
}`,
31+
},
32+
}
33+
test.Prepare(cmdConfig, commands)
34+
35+
testConfig := test.Config{
36+
BaseUrl: "http://localhost:8082/admin/databases",
37+
CommonHeaders: test.Headers{"Authorization": {adminToken}},
38+
}
39+
40+
tests := []test.Test{
41+
// read table and verify table and column comments are returned
42+
{
43+
Description: "table and column comments are returned on GET",
44+
Query: "/dbtest/tables/comments_test",
45+
Expected: `{"name":"comments_test","schema":"public","owner":"admin","comment":"This is a test table","rowsecurity":false,"columns":[{"name":"id","type":"int4","notnull":true,"default":null,"comment":null,"constraints":null,"table":"comments_test","schema":"public"},{"name":"name","type":"text","notnull":false,"default":null,"comment":"The name field","constraints":null,"table":"comments_test","schema":"public"}],"constraints":null,"hasindexes":false,"hastriggers":false,"ispartition":false}`,
46+
Status: 200,
47+
},
48+
// update table comment via PATCH
49+
{
50+
Description: "can update table comment",
51+
Method: "PATCH",
52+
Query: "/dbtest/tables/comments_test",
53+
Body: `{"comment": "Updated table comment"}`,
54+
Status: 201,
55+
},
56+
// update column comment via PATCH
57+
{
58+
Description: "can update column comment",
59+
Method: "PATCH",
60+
Query: "/dbtest/tables/comments_test/columns/id",
61+
Body: `{"comment": "The primary key"}`,
62+
Status: 200,
63+
},
64+
// verify updated comments
65+
{
66+
Description: "updated comments are returned on GET",
67+
Query: "/dbtest/tables/comments_test",
68+
Expected: `{"name":"comments_test","schema":"public","owner":"admin","comment":"Updated table comment","rowsecurity":false,"columns":[{"name":"id","type":"int4","notnull":true,"default":null,"comment":"The primary key","constraints":null,"table":"comments_test","schema":"public"},{"name":"name","type":"text","notnull":false,"default":null,"comment":"The name field","constraints":null,"table":"comments_test","schema":"public"}],"constraints":null,"hasindexes":false,"hastriggers":false,"ispartition":false}`,
69+
Status: 200,
70+
},
71+
}
72+
73+
test.Execute(t, testConfig, tests)
74+
}

test/api/weird_names_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func TestWeirdNames(t *testing.T) {
7878
Method: "GET",
7979
Query: "/databases/dbtest/tables/table1",
8080
Body: ``,
81-
Expected: `{"name":"table1","schema":"public","owner":"admin","rowsecurity":false,"hasindexes":false,"hastriggers":false,"ispartition":false,"constraints":null,"columns":[{"name":"a.a","type":"int4","notnull":false,"default":null,"constraints":null,"table":"table1","schema":"public"}]}`,
81+
Expected: `{"name":"table1","schema":"public","owner":"admin","comment":null,"rowsecurity":false,"hasindexes":false,"hastriggers":false,"ispartition":false,"constraints":null,"columns":[{"name":"a.a","type":"int4","notnull":false,"default":null,"comment":null,"constraints":null,"table":"table1","schema":"public"}]}`,
8282
Status: 200,
8383
},
8484
{
@@ -140,7 +140,7 @@ func TestWeirdNames(t *testing.T) {
140140
Method: "GET",
141141
Query: "/databases/dbtest/tables/table2",
142142
Body: ``,
143-
Expected: `{"name":"table2","schema":"public","owner":"admin","rowsecurity":false,"hasindexes":false,"hastriggers":false,"ispartition":false,"constraints":null,"columns":[{"name":"a\"a","type":"int4","notnull":false,"default":null,"constraints":null,"table":"table2","schema":"public"}]}`,
143+
Expected: `{"name":"table2","schema":"public","owner":"admin","comment":null,"rowsecurity":false,"hasindexes":false,"hastriggers":false,"ispartition":false,"constraints":null,"columns":[{"name":"a\"a","type":"int4","notnull":false,"default":null,"comment":null,"constraints":null,"table":"table2","schema":"public"}]}`,
144144
Status: 200,
145145
},
146146
{

0 commit comments

Comments
 (0)