Initial support for multiple schemas

This commit is contained in:
Dan Sosedoff
2016-01-12 21:33:44 -06:00
parent 9c7eaf63d5
commit 9ffa05affb
11 changed files with 410 additions and 137 deletions
+13 -7
View File
@@ -112,6 +112,17 @@ func GetDatabases(c *gin.Context) {
serveResult(names, err, c)
}
func GetObjects(c *gin.Context) {
result, err := DB(c).Objects()
if err != nil {
c.JSON(400, NewError(err))
return
}
objects := client.ObjectsFromResult(result)
c.JSON(200, objects)
}
func RunQuery(c *gin.Context) {
query := strings.TrimSpace(c.Request.FormValue("query"))
@@ -135,13 +146,8 @@ func ExplainQuery(c *gin.Context) {
}
func GetSchemas(c *gin.Context) {
names, err := DB(c).Schemas()
serveResult(names, err, c)
}
func GetTables(c *gin.Context) {
names, err := DB(c).Tables()
serveResult(names, err, c)
res, err := DB(c).Schemas()
serveResult(res, err, c)
}
func GetTable(c *gin.Context) {
+1 -1
View File
@@ -32,7 +32,7 @@ func SetupRoutes(router *gin.Engine) {
api.GET("/sequences", GetSequences)
api.GET("/activity", GetActivity)
api.GET("/schemas", GetSchemas)
api.GET("/tables", GetTables)
api.GET("/objects", GetObjects)
api.GET("/tables/:table", GetTable)
api.GET("/tables/:table/rows", GetTableRows)
api.GET("/tables/:table/info", GetTableInfo)
+21 -7
View File
@@ -3,6 +3,7 @@ package client
import (
"fmt"
"reflect"
"strings"
_ "github.com/lib/pq"
@@ -28,6 +29,14 @@ type RowsOptions struct {
SortOrder string // Sort direction (ASC, DESC)
}
func getSchemaAndTable(str string) (string, string) {
chunks := strings.Split(str, ".")
if len(chunks) == 1 {
return "public", chunks[0]
}
return chunks[0], chunks[1]
}
func New() (*Client, error) {
str, err := connection.BuildString(command.Opts)
@@ -89,16 +98,18 @@ func (client *Client) Schemas() ([]string, error) {
return client.fetchRows(statements.PG_SCHEMAS)
}
func (client *Client) Tables() ([]string, error) {
return client.fetchRows(statements.PG_TABLES)
func (client *Client) Objects() (*Result, error) {
return client.query(statements.PG_OBJECTS)
}
func (client *Client) Table(table string) (*Result, error) {
return client.query(statements.PG_TABLE_SCHEMA, table)
schema, table := getSchemaAndTable(table)
return client.query(statements.PG_TABLE_SCHEMA, schema, table)
}
func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error) {
sql := fmt.Sprintf(`SELECT * FROM "%s"`, table)
schema, table := getSchemaAndTable(table)
sql := fmt.Sprintf(`SELECT * FROM "%s"."%s"`, schema, table)
if opts.Where != "" {
sql += fmt.Sprintf(" WHERE %s", opts.Where)
@@ -124,7 +135,8 @@ func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error)
}
func (client *Client) TableRowsCount(table string, opts RowsOptions) (*Result, error) {
sql := fmt.Sprintf(`SELECT COUNT(1) FROM "%s"`, table)
schema, table := getSchemaAndTable(table)
sql := fmt.Sprintf(`SELECT COUNT(1) FROM "%s"."%s"`, schema, table)
if opts.Where != "" {
sql += fmt.Sprintf(" WHERE %s", opts.Where)
@@ -138,7 +150,8 @@ func (client *Client) TableInfo(table string) (*Result, error) {
}
func (client *Client) TableIndexes(table string) (*Result, error) {
res, err := client.query(statements.PG_TABLE_INDEXES, table)
schema, table := getSchemaAndTable(table)
res, err := client.query(statements.PG_TABLE_INDEXES, schema, table)
if err != nil {
return nil, err
@@ -148,7 +161,8 @@ func (client *Client) TableIndexes(table string) (*Result, error) {
}
func (client *Client) TableConstraints(table string) (*Result, error) {
res, err := client.query(statements.PG_TABLE_CONSTRAINTS, table)
schema, table := getSchemaAndTable(table)
res, err := client.query(statements.PG_TABLE_CONSTRAINTS, schema, table)
if err != nil {
return nil, err
+18 -7
View File
@@ -15,6 +15,14 @@ var (
testCommands map[string]string
)
func mapKeys(data map[string]*Objects) []string {
result := []string{}
for k, _ := range data {
result = append(result, k)
}
return result
}
func setupCommands() {
testCommands = map[string]string{
"createdb": "createdb",
@@ -112,10 +120,11 @@ func test_Databases(t *testing.T) {
assert.Contains(t, res, "postgres")
}
func test_Tables(t *testing.T) {
res, err := testClient.Tables()
func test_Objects(t *testing.T) {
res, err := testClient.Objects()
objects := ObjectsFromResult(res)
expected := []string{
tables := []string{
"alternate_stock",
"authors",
"book_backup",
@@ -132,19 +141,21 @@ func test_Tables(t *testing.T) {
"my_list",
"numeric_values",
"publishers",
"recent_shipments",
"schedules",
"shipments",
"states",
"stock",
"stock_backup",
"stock_view",
"subjects",
"text_sorting",
}
assert.Equal(t, nil, err)
assert.Equal(t, expected, res)
assert.Equal(t, []string{"schema", "name", "type", "owner"}, res.Columns)
assert.Equal(t, []string{"public"}, mapKeys(objects))
assert.Equal(t, tables, objects["public"].Tables)
assert.Equal(t, []string{"recent_shipments", "stock_view"}, objects["public"].Views)
assert.Equal(t, []string{"author_ids", "book_ids", "shipments_ship_id_seq", "subject_ids"}, objects["public"].Sequences)
}
func test_Table(t *testing.T) {
@@ -284,7 +295,7 @@ func TestAll(t *testing.T) {
test_Test(t)
test_Info(t)
test_Databases(t)
test_Tables(t)
test_Objects(t)
test_Table(t)
test_TableRows(t)
test_TableInfo(t)
+35
View File
@@ -24,6 +24,12 @@ type Result struct {
Rows []Row `json:"rows"`
}
type Objects struct {
Tables []string `json:"tables"`
Views []string `json:"views"`
Sequences []string `json:"sequences"`
}
// Due to big int number limitations in javascript, numbers should be encoded
// as strings so they could be properly loaded on the frontend.
func (res *Result) PrepareBigints() {
@@ -98,3 +104,32 @@ func (res *Result) JSON() []byte {
data, _ := json.Marshal(res.Format())
return data
}
func ObjectsFromResult(res *Result) map[string]*Objects {
objects := map[string]*Objects{}
for _, row := range res.Rows {
schema := row[0].(string)
name := row[1].(string)
object_type := row[2].(string)
if objects[schema] == nil {
objects[schema] = &Objects{
Tables: []string{},
Views: []string{},
Sequences: []string{},
}
}
switch object_type {
case "table":
objects[schema].Tables = append(objects[schema].Tables, name)
case "view":
objects[schema].Views = append(objects[schema].Views, name)
case "sequence":
objects[schema].Sequences = append(objects[schema].Sequences, name)
}
}
return objects
}
+6 -6
View File
File diff suppressed because one or more lines are too long
+123 -34
View File
@@ -1,49 +1,110 @@
package statements
const (
PG_DATABASES = `SELECT datname FROM pg_database WHERE NOT datistemplate ORDER BY datname ASC`
// ---------------------------------------------------------------------------
PG_SCHEMAS = `SELECT schema_name FROM information_schema.schemata ORDER BY schema_name ASC`
PG_DATABASES = `
SELECT
datname
FROM
pg_database
WHERE
NOT datistemplate
ORDER BY
datname ASC`
PG_INFO = `SELECT
session_user
, current_user
, current_database()
, current_schemas(false)
, inet_client_addr()
, inet_client_port()
, inet_server_addr()
, inet_server_port()
, version()`
// ---------------------------------------------------------------------------
PG_TABLE_INDEXES = `SELECT indexname, indexdef FROM pg_indexes WHERE tablename = $1`
PG_SCHEMAS = `
SELECT
schema_name
FROM
information_schema.schemata
ORDER BY
schema_name ASC`
PG_TABLE_CONSTRAINTS = `SELECT
// ---------------------------------------------------------------------------
PG_INFO = `
SELECT
session_user,
current_user,
current_database(),
current_schemas(false),
inet_client_addr(),
inet_client_port(),
inet_server_addr(),
inet_server_port(),
version()`
// ---------------------------------------------------------------------------
PG_TABLE_INDEXES = `
SELECT
indexname, indexdef
FROM
pg_indexes
WHERE
schemaname = $1 AND
tablename = $2`
// ---------------------------------------------------------------------------
PG_TABLE_CONSTRAINTS = `
SELECT
pg_get_constraintdef(c.oid, true) as condef
FROM pg_constraint c
JOIN pg_namespace n ON n.oid = c.connamespace
JOIN pg_class cl ON cl.oid = c.conrelid
WHERE n.nspname = 'public'
AND relname = $1
ORDER BY contype desc`
FROM
pg_constraint c
JOIN
pg_namespace n ON n.oid = c.connamespace
JOIN
pg_class cl ON cl.oid = c.conrelid
WHERE
n.nspname = $1 AND
relname = $2
ORDER BY
contype desc`
// ---------------------------------------------------------------------------
PG_TABLE_INFO = `SELECT
pg_size_pretty(pg_table_size($1)) AS data_size
, pg_size_pretty(pg_indexes_size($1)) AS index_size
, pg_size_pretty(pg_total_relation_size($1)) AS total_size
, (SELECT reltuples FROM pg_class WHERE oid = $1::regclass) AS rows_count`
PG_TABLE_INFO = `
SELECT
pg_size_pretty(pg_table_size($1)) AS data_size,
pg_size_pretty(pg_indexes_size($1)) AS index_size,
pg_size_pretty(pg_total_relation_size($1)) AS total_size,
(SELECT reltuples FROM pg_class WHERE oid = $1::regclass) AS rows_count`
PG_TABLE_SCHEMA = `SELECT
column_name, data_type, is_nullable, character_maximum_length, character_set_catalog, column_default
FROM information_schema.columns
WHERE table_name = $1`
// ---------------------------------------------------------------------------
PG_TABLES = `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_schema,table_name`
PG_TABLE_SCHEMA = `
SELECT
column_name,
data_type,
is_nullable,
character_maximum_length,
character_set_catalog,
column_default
FROM
information_schema.columns
WHERE
table_schema = $1 AND
table_name = $2`
PG_SEQUENCES = `SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'public' ORDER BY sequence_name`
// ---------------------------------------------------------------------------
PG_ACTIVITY = `SELECT
PG_SEQUENCES = `
SELECT
sequence_name
FROM
information_schema.sequences
WHERE
sequence_schema = 'public'
ORDER BY sequence_name`
// ---------------------------------------------------------------------------
PG_ACTIVITY = `
SELECT
datname,
query,
state,
@@ -54,6 +115,34 @@ WHERE table_name = $1`
datid,
application_name,
client_addr
FROM pg_stat_activity
WHERE state IS NOT NULL`
FROM
pg_stat_activity
WHERE
state IS NOT NULL`
// ---------------------------------------------------------------------------
PG_OBJECTS = `
SELECT
n.nspname as "schema",
c.relname as "name",
CASE c.relkind
WHEN 'r' THEN 'table'
WHEN 'v' THEN 'view'
WHEN 'm' THEN 'materialized_view'
WHEN 'i' THEN 'index'
WHEN 'S' THEN 'sequence'
WHEN 's' THEN 'special'
WHEN 'f' THEN 'foreign_table'
END as "type",
pg_catalog.pg_get_userbyid(c.relowner) as "owner"
FROM
pg_catalog.pg_class c
LEFT JOIN
pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE
c.relkind IN ('r','v','S','s','') AND
n.nspname !~ '^pg_toast' AND
n.nspname NOT IN ('information_schema', 'pg_catalog')
ORDER BY 1, 2`
)