Merge with master
This commit is contained in:
commit
9bfaae7194
13
.travis.yml
13
.travis.yml
@ -1,16 +1,21 @@
|
||||
sudo: required
|
||||
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
|
||||
go:
|
||||
- 1.4.2
|
||||
- 1.5.2
|
||||
- 1.4.2
|
||||
- 1.5.2
|
||||
|
||||
install:
|
||||
- make setup
|
||||
|
||||
script:
|
||||
- make build
|
||||
- make test
|
||||
- make test
|
||||
- PGHOST=127.0.0.1 ./script/test_all.sh
|
@ -907,6 +907,8 @@ CREATE FUNCTION "check_book_addition" () RETURNS opaque AS '
|
||||
|
||||
CREATE VIEW "stock_view" as SELECT stock.isbn, stock.retail, stock.stock FROM stock;
|
||||
|
||||
CREATE MATERIALIZED VIEW "m_stock_view" as SELECT stock.isbn, stock.retail, stock.stock FROM stock;
|
||||
|
||||
--
|
||||
-- TOC Entry ID 30 (OID 3628247)
|
||||
--
|
||||
|
@ -158,7 +158,15 @@ func GetSchemas(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetTable(c *gin.Context) {
|
||||
res, err := DB(c).Table(c.Params.ByName("table"))
|
||||
var res *client.Result
|
||||
var err error
|
||||
|
||||
if c.Request.FormValue("type") == "materialized_view" {
|
||||
res, err = DB(c).MaterializedView(c.Params.ByName("table"))
|
||||
} else {
|
||||
res, err = DB(c).Table(c.Params.ByName("table"))
|
||||
}
|
||||
|
||||
serveResult(res, err, c)
|
||||
}
|
||||
|
||||
|
@ -141,6 +141,10 @@ func (client *Client) Table(table string) (*Result, error) {
|
||||
return client.query(statements.PG_TABLE_SCHEMA, schema, table)
|
||||
}
|
||||
|
||||
func (client *Client) MaterializedView(name string) (*Result, error) {
|
||||
return client.query(statements.PG_MATERIALIZED_VIEW_SCHEMA, name)
|
||||
}
|
||||
|
||||
func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error) {
|
||||
schema, table := getSchemaAndTable(table)
|
||||
sql := fmt.Sprintf(`SELECT * FROM "%s"."%s"`, schema, table)
|
||||
@ -235,7 +239,10 @@ func (client *Client) query(query string, args ...interface{}) (*Result, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := Result{Columns: cols}
|
||||
result := Result{
|
||||
Columns: cols,
|
||||
Rows: []Row{},
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
obj, err := rows.SliceScan()
|
||||
|
@ -11,8 +11,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
testClient *Client
|
||||
testCommands map[string]string
|
||||
testClient *Client
|
||||
testCommands map[string]string
|
||||
serverHost string
|
||||
serverPort string
|
||||
serverUser string
|
||||
serverPassword string
|
||||
serverDatabase string
|
||||
)
|
||||
|
||||
func mapKeys(data map[string]*Objects) []string {
|
||||
@ -23,6 +28,28 @@ func mapKeys(data map[string]*Objects) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func pgVersion() (int, int) {
|
||||
var major, minor int
|
||||
fmt.Sscanf(os.Getenv("PGVERSION"), "%d.%d", &major, &minor)
|
||||
return major, minor
|
||||
}
|
||||
|
||||
func getVar(name, def string) string {
|
||||
val := os.Getenv(name)
|
||||
if val == "" {
|
||||
return def
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func initVars() {
|
||||
serverHost = getVar("PGHOST", "localhost")
|
||||
serverPort = getVar("PGPORT", "5432")
|
||||
serverUser = getVar("PGUSER", "postgres")
|
||||
serverPassword = getVar("PGPASSWORD", "postgres")
|
||||
serverDatabase = getVar("PGDATABASE", "booktown")
|
||||
}
|
||||
|
||||
func setupCommands() {
|
||||
testCommands = map[string]string{
|
||||
"createdb": "createdb",
|
||||
@ -42,7 +69,13 @@ func onWindows() bool {
|
||||
}
|
||||
|
||||
func setup() {
|
||||
out, err := exec.Command(testCommands["createdb"], "-U", "postgres", "-h", "localhost", "booktown").CombinedOutput()
|
||||
out, err := exec.Command(
|
||||
testCommands["createdb"],
|
||||
"-U", serverUser,
|
||||
"-h", serverHost,
|
||||
"-p", serverPort,
|
||||
serverDatabase,
|
||||
).CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Database creation failed:", string(out))
|
||||
@ -50,7 +83,14 @@ func setup() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
out, err = exec.Command(testCommands["psql"], "-U", "postgres", "-h", "localhost", "-f", "../../data/booktown.sql", "booktown").CombinedOutput()
|
||||
out, err = exec.Command(
|
||||
testCommands["psql"],
|
||||
"-U", serverUser,
|
||||
"-h", serverHost,
|
||||
"-p", serverPort,
|
||||
"-f", "../../data/booktown.sql",
|
||||
serverDatabase,
|
||||
).CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Database import failed:", string(out))
|
||||
@ -60,7 +100,8 @@ func setup() {
|
||||
}
|
||||
|
||||
func setupClient() {
|
||||
testClient, _ = NewFromUrl("postgres://postgres@localhost/booktown?sslmode=disable", nil)
|
||||
url := fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase)
|
||||
testClient, _ = NewFromUrl(url, nil)
|
||||
}
|
||||
|
||||
func teardownClient() {
|
||||
@ -70,7 +111,13 @@ func teardownClient() {
|
||||
}
|
||||
|
||||
func teardown() {
|
||||
_, err := exec.Command(testCommands["dropdb"], "-U", "postgres", "-h", "localhost", "booktown").CombinedOutput()
|
||||
_, err := exec.Command(
|
||||
testCommands["dropdb"],
|
||||
"-U", serverUser,
|
||||
"-h", serverHost,
|
||||
"-p", serverPort,
|
||||
serverDatabase,
|
||||
).CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Teardown error:", err)
|
||||
@ -78,7 +125,7 @@ func teardown() {
|
||||
}
|
||||
|
||||
func test_NewClientFromUrl(t *testing.T) {
|
||||
url := "postgres://postgres@localhost/booktown?sslmode=disable"
|
||||
url := fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase)
|
||||
client, err := NewFromUrl(url, nil)
|
||||
|
||||
if err != nil {
|
||||
@ -90,7 +137,7 @@ func test_NewClientFromUrl(t *testing.T) {
|
||||
}
|
||||
|
||||
func test_NewClientFromUrl2(t *testing.T) {
|
||||
url := "postgresql://postgres@localhost/booktown?sslmode=disable"
|
||||
url := fmt.Sprintf("postgresql://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase)
|
||||
client, err := NewFromUrl(url, nil)
|
||||
|
||||
if err != nil {
|
||||
@ -156,6 +203,13 @@ func test_Objects(t *testing.T) {
|
||||
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)
|
||||
|
||||
major, minor := pgVersion()
|
||||
if minor == 0 || minor >= 3 {
|
||||
assert.Equal(t, []string{"m_stock_view"}, objects["public"].MaterializedViews)
|
||||
} else {
|
||||
t.Logf("Skipping materialized view on %d.%d\n", major, minor)
|
||||
}
|
||||
}
|
||||
|
||||
func test_Table(t *testing.T) {
|
||||
@ -257,7 +311,8 @@ func test_HistoryError(t *testing.T) {
|
||||
}
|
||||
|
||||
func test_HistoryUniqueness(t *testing.T) {
|
||||
client, _ := NewFromUrl("postgres://postgres@localhost/booktown?sslmode=disable", nil)
|
||||
url := fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=disable", serverUser, serverHost, serverPort, serverDatabase)
|
||||
client, _ := NewFromUrl(url, nil)
|
||||
|
||||
client.Query("SELECT * FROM books WHERE id = 1")
|
||||
client.Query("SELECT * FROM books WHERE id = 1")
|
||||
@ -272,6 +327,7 @@ func TestAll(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
initVars()
|
||||
setupCommands()
|
||||
teardown()
|
||||
setup()
|
||||
|
@ -25,9 +25,10 @@ type Result struct {
|
||||
}
|
||||
|
||||
type Objects struct {
|
||||
Tables []string `json:"tables"`
|
||||
Views []string `json:"views"`
|
||||
Sequences []string `json:"sequences"`
|
||||
Tables []string `json:"table"`
|
||||
Views []string `json:"view"`
|
||||
MaterializedViews []string `json:"materialized_view"`
|
||||
Sequences []string `json:"sequence"`
|
||||
}
|
||||
|
||||
// Due to big int number limitations in javascript, numbers should be encoded
|
||||
@ -115,9 +116,10 @@ func ObjectsFromResult(res *Result) map[string]*Objects {
|
||||
|
||||
if objects[schema] == nil {
|
||||
objects[schema] = &Objects{
|
||||
Tables: []string{},
|
||||
Views: []string{},
|
||||
Sequences: []string{},
|
||||
Tables: []string{},
|
||||
Views: []string{},
|
||||
MaterializedViews: []string{},
|
||||
Sequences: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,6 +128,8 @@ func ObjectsFromResult(res *Result) map[string]*Objects {
|
||||
objects[schema].Tables = append(objects[schema].Tables, name)
|
||||
case "view":
|
||||
objects[schema].Views = append(objects[schema].Views, name)
|
||||
case "materialized_view":
|
||||
objects[schema].MaterializedViews = append(objects[schema].MaterializedViews, name)
|
||||
case "sequence":
|
||||
objects[schema].Sequences = append(objects[schema].Sequences, name)
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -92,6 +92,23 @@ WHERE
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
PG_MATERIALIZED_VIEW_SCHEMA = `
|
||||
SELECT
|
||||
attname as column_name,
|
||||
atttypid::regtype AS data_type,
|
||||
(case when attnotnull IS TRUE then 'NO' else 'YES' end) as is_nullable,
|
||||
null as character_maximum_length,
|
||||
null as character_set_catalog,
|
||||
null as column_default
|
||||
FROM
|
||||
pg_attribute
|
||||
WHERE
|
||||
attrelid = $1::regclass AND
|
||||
attnum > 0 AND
|
||||
NOT attisdropped`
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
PG_ACTIVITY = `
|
||||
SELECT
|
||||
datname,
|
||||
@ -130,7 +147,7 @@ FROM
|
||||
LEFT JOIN
|
||||
pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE
|
||||
c.relkind IN ('r','v','S','s','') AND
|
||||
c.relkind IN ('r','v','m','S','s','') AND
|
||||
n.nspname !~ '^pg_toast' AND
|
||||
n.nspname NOT IN ('information_schema', 'pg_catalog')
|
||||
ORDER BY 1, 2`
|
||||
|
20
script/test_all.sh
Executable file
20
script/test_all.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export PGHOST=${PGHOST:-192.168.99.100}
|
||||
export PGUSER="postgres"
|
||||
export PGPASSWORD=""
|
||||
export PGDATABASE="booktown"
|
||||
export PGPORT="15432"
|
||||
|
||||
for i in {1..5}
|
||||
do
|
||||
export PGVERSION="9.$i"
|
||||
echo "Running tests against PostgreSQL v$PGVERSION"
|
||||
docker rm -f postgres || true
|
||||
docker run -p $PGPORT:5432 --name postgres -e POSTGRES_PASSWORD=$PGPASSWORD -d postgres:$PGVERSION
|
||||
sleep 5
|
||||
make test
|
||||
echo "----------"
|
||||
done
|
@ -2,7 +2,7 @@ var editor = null;
|
||||
var connected = false;
|
||||
var bookmarks = {};
|
||||
var default_rows_limit = 100;
|
||||
var currentTable = null;
|
||||
var currentObject = null;
|
||||
|
||||
var filterOptions = {
|
||||
"equal": "= 'DATA'",
|
||||
@ -23,11 +23,11 @@ function guid() {
|
||||
}
|
||||
|
||||
function getSessionId() {
|
||||
var id = localStorage.getItem("session_id");
|
||||
var id = sessionStorage.getItem("session_id");
|
||||
|
||||
if (!id) {
|
||||
id = guid();
|
||||
localStorage.setItem("session_id", id);
|
||||
sessionStorage.setItem("session_id", id);
|
||||
}
|
||||
|
||||
return id;
|
||||
@ -76,16 +76,16 @@ function apiCall(method, path, params, cb) {
|
||||
});
|
||||
}
|
||||
|
||||
function getObjects(cb) { apiCall("get", "/objects", {}, cb); }
|
||||
function getTables(cb) { apiCall("get", "/tables", {}, cb); }
|
||||
function getTableRows(table, opts, cb) { apiCall("get", "/tables/" + table + "/rows", opts, cb); }
|
||||
function getTableStructure(table, cb) { apiCall("get", "/tables/" + table, {}, cb); }
|
||||
function getTableIndexes(table, cb) { apiCall("get", "/tables/" + table + "/indexes", {}, cb); }
|
||||
function getTableConstraints(table, cb) { apiCall("get", "/tables/" + table + "/constraints", {}, cb); }
|
||||
function getHistory(cb) { apiCall("get", "/history", {}, cb); }
|
||||
function getBookmarks(cb) { apiCall("get", "/bookmarks", {}, cb); }
|
||||
function executeQuery(query, cb) { apiCall("post", "/query", { query: query }, cb); }
|
||||
function explainQuery(query, cb) { apiCall("post", "/explain", { query: query }, cb); }
|
||||
function getObjects(cb) { apiCall("get", "/objects", {}, cb); }
|
||||
function getTables(cb) { apiCall("get", "/tables", {}, cb); }
|
||||
function getTableRows(table, opts, cb) { apiCall("get", "/tables/" + table + "/rows", opts, cb); }
|
||||
function getTableStructure(table, opts, cb) { apiCall("get", "/tables/" + table, opts, cb); }
|
||||
function getTableIndexes(table, cb) { apiCall("get", "/tables/" + table + "/indexes", {}, cb); }
|
||||
function getTableConstraints(table, cb) { apiCall("get", "/tables/" + table + "/constraints", {}, cb); }
|
||||
function getHistory(cb) { apiCall("get", "/history", {}, cb); }
|
||||
function getBookmarks(cb) { apiCall("get", "/bookmarks", {}, cb); }
|
||||
function executeQuery(query, cb) { apiCall("post", "/query", { query: query }, cb); }
|
||||
function explainQuery(query, cb) { apiCall("post", "/explain", { query: query }, cb); }
|
||||
|
||||
function encodeQuery(query) {
|
||||
return window.btoa(query);
|
||||
@ -95,15 +95,17 @@ function buildSchemaSection(name, objects) {
|
||||
var section = "";
|
||||
|
||||
var titles = {
|
||||
"tables": "Tables",
|
||||
"views": "Views",
|
||||
"sequences": "Sequences"
|
||||
"table": "Tables",
|
||||
"view": "Views",
|
||||
"materialized_view": "Materialized Views",
|
||||
"sequence": "Sequences"
|
||||
};
|
||||
|
||||
var icons = {
|
||||
"tables": '<i class="fa fa-table"></i>',
|
||||
"views": '<i class="fa fa-table"></i>',
|
||||
"sequences": '<i class="fa fa-circle-o"></i>'
|
||||
"table": '<i class="fa fa-table"></i>',
|
||||
"view": '<i class="fa fa-table"></i>',
|
||||
"materialized_view": '<i class="fa fa-table"></i>',
|
||||
"sequence": '<i class="fa fa-circle-o"></i>'
|
||||
};
|
||||
|
||||
var klass = "";
|
||||
@ -113,11 +115,11 @@ function buildSchemaSection(name, objects) {
|
||||
section += "<div class='schema-name'><i class='fa fa-folder-o'></i><i class='fa fa-folder-open-o'></i> " + name + "</div>";
|
||||
section += "<div class='schema-container'>";
|
||||
|
||||
for (group of ["tables", "views", "sequences"]) {
|
||||
for (group of ["table", "view", "materialized_view", "sequence"]) {
|
||||
if (objects[group].length == 0) continue;
|
||||
|
||||
group_klass = "";
|
||||
if (name == "public" && group == "tables") group_klass = "expanded";
|
||||
if (name == "public" && group == "table") group_klass = "expanded";
|
||||
|
||||
section += "<div class='schema-group " + group_klass + "'>";
|
||||
section += "<div class='schema-group-title'><i class='fa fa-chevron-right'></i><i class='fa fa-chevron-down'></i> " + titles[group] + " (" + objects[group].length + ")</div>";
|
||||
@ -165,8 +167,8 @@ function unescapeHtml(str){
|
||||
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
|
||||
}
|
||||
|
||||
function getCurrentTable() {
|
||||
return currentTable;
|
||||
function getCurrentObject() {
|
||||
return currentObject || { name: "", type: "" };
|
||||
}
|
||||
|
||||
function resetTable() {
|
||||
@ -278,7 +280,7 @@ function showQueryHistory() {
|
||||
}
|
||||
|
||||
function showTableIndexes() {
|
||||
var name = getCurrentTable();
|
||||
var name = getCurrentObject().name;
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
@ -296,7 +298,7 @@ function showTableIndexes() {
|
||||
}
|
||||
|
||||
function showTableConstraints() {
|
||||
var name = getCurrentTable();
|
||||
var name = getCurrentObject().name;
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
@ -314,7 +316,7 @@ function showTableConstraints() {
|
||||
}
|
||||
|
||||
function showTableInfo() {
|
||||
var name = getCurrentTable();
|
||||
var name = getCurrentObject().name;
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
@ -330,7 +332,7 @@ function showTableInfo() {
|
||||
$("#table_encoding").text("Unknown");
|
||||
});
|
||||
|
||||
buildTableFilters(name);
|
||||
buildTableFilters(name, getCurrentObject().type);
|
||||
}
|
||||
|
||||
function updatePaginator(pagination) {
|
||||
@ -365,7 +367,7 @@ function updatePaginator(pagination) {
|
||||
}
|
||||
|
||||
function showTableContent(sortColumn, sortOrder) {
|
||||
var name = getCurrentTable();
|
||||
var name = getCurrentObject().name;
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
@ -407,7 +409,7 @@ function showTableContent(sortColumn, sortOrder) {
|
||||
}
|
||||
|
||||
function showTableStructure() {
|
||||
var name = getCurrentTable();
|
||||
var name = getCurrentObject().name;
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
@ -419,7 +421,9 @@ function showTableStructure() {
|
||||
$("#input").hide();
|
||||
$("#body").prop("class", "full");
|
||||
|
||||
getTableStructure(name, function(data) {
|
||||
console.log(getCurrentObject());
|
||||
|
||||
getTableStructure(name, { type: getCurrentObject().type }, function(data) {
|
||||
buildTable(data);
|
||||
$("#results").addClass("no-crop");
|
||||
});
|
||||
@ -537,11 +541,14 @@ function exportTo(format) {
|
||||
win.focus();
|
||||
}
|
||||
|
||||
function buildTableFilters(name) {
|
||||
getTableStructure(name, function(data) {
|
||||
function buildTableFilters(name, type) {
|
||||
getTableStructure(name, { type: type }, function(data) {
|
||||
if (data.rows.length == 0) {
|
||||
$("#pagination .filters").hide();
|
||||
}
|
||||
else {
|
||||
$("#pagination .filters").show();
|
||||
}
|
||||
|
||||
$("#pagination select.column").html("<option value='' selected>Select column</option>");
|
||||
|
||||
@ -731,7 +738,10 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
$("#objects").on("click", "li", function(e) {
|
||||
currentTable = $(this).data("id");
|
||||
currentObject = {
|
||||
name: $(this).data("id"),
|
||||
type: $(this).data("type")
|
||||
};
|
||||
|
||||
$("#objects li").removeClass("active");
|
||||
$(this).addClass("active");
|
||||
|
Loading…
x
Reference in New Issue
Block a user