From f098b0301ab175f6064f8f0db1925e08fed00e49 Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Fri, 29 Aug 2025 00:59:24 +0530 Subject: [PATCH 1/4] feat: Implement Flow.bi custom constraints query with safe fallback to standard PostgreSQL constraints. - Dynamically select the appropriate constraints based on environment settings and table availability. - Ensure robust handling of both custom and standard constraints. --- pkg/statements/sql/table_constraints.sql | 71 +++++++++++++++++++----- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/pkg/statements/sql/table_constraints.sql b/pkg/statements/sql/table_constraints.sql index 39931af3e..2814561fa 100644 --- a/pkg/statements/sql/table_constraints.sql +++ b/pkg/statements/sql/table_constraints.sql @@ -1,14 +1,57 @@ -SELECT - conname AS name, - pg_get_constraintdef(c.oid, true) AS definition -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 +-- Flow.bi Custom Constraints Query with Safe Fallback +-- This query conditionally shows either Flow.bi custom constraints or real PostgreSQL constraints +-- based on environment variable and table availability + +WITH flowbi_constraints AS ( + -- Step 1: Determine which mode to use based on safety checks + SELECT + CASE + -- Check if Flow.bi mode is enabled via session variable + WHEN current_setting('pgweb.custom_constraints', true) = 'true' + -- Verify the Flow.bi constraints table exists + AND EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = 'intf_studio' + AND table_name = 'pgweb_constraints' + ) + -- Verify all required columns exist (exactly 4 expected columns) + AND ( + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_schema = 'intf_studio' + AND table_name = 'pgweb_constraints' + AND column_name IN ('conname', 'definition', 'nspname', 'relname') + ) = 4 + THEN 'use_flowbi' + ELSE 'use_standard' + END AS mode +), +flowbi_data AS ( + -- Step 2: Query Flow.bi custom constraints (only when safe) + SELECT + fc.conname AS name, + fc.definition + FROM flowbi_constraints fc_mode + CROSS JOIN intf_studio.pgweb_constraints fc + WHERE fc_mode.mode = 'use_flowbi' + AND fc.nspname = $1 -- Schema parameter + AND fc.relname = $2 -- Table parameter +), +standard_data AS ( + -- Step 3: Query real PostgreSQL constraints (fallback mode) + SELECT + c.conname AS name, + pg_get_constraintdef(c.oid, true) AS definition + FROM flowbi_constraints fc_mode + CROSS JOIN pg_constraint c + JOIN pg_namespace n ON n.oid = c.connamespace + JOIN pg_class cl ON cl.oid = c.conrelid + WHERE fc_mode.mode = 'use_standard' + AND n.nspname = $1 -- Schema parameter + AND cl.relname = $2 -- Table parameter +) +-- Step 4: Return results from either Flow.bi or standard mode (never both) +SELECT name, definition FROM flowbi_data +UNION ALL +SELECT name, definition FROM standard_data +ORDER BY name From 74386d4fc4f2bcdd7ae32a0976b9162a4f8bd6c8 Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Fri, 29 Aug 2025 13:27:19 +0530 Subject: [PATCH 2/4] feat: Implement Runtime Query File Replacement for custom constraints Ref #43 --- pkg/statements/sql.go | 19 +++++++ pkg/statements/sql/table_constraints.sql | 71 +++++------------------- 2 files changed, 33 insertions(+), 57 deletions(-) diff --git a/pkg/statements/sql.go b/pkg/statements/sql.go index a12e1c0f3..3e8d136c8 100644 --- a/pkg/statements/sql.go +++ b/pkg/statements/sql.go @@ -2,6 +2,9 @@ package statements import ( _ "embed" + "log" + "os" + "path/filepath" ) var ( @@ -24,6 +27,8 @@ var ( TableIndexes string //go:embed sql/table_constraints.sql + tableConstraintsEmbedded string + TableConstraints string //go:embed sql/table_info.sql @@ -61,3 +66,17 @@ var ( "9.6": "SELECT datname, query, state, wait_event, wait_event_type, query_start, state_change, pid, datid, application_name, client_addr FROM pg_stat_activity WHERE datname = current_database() and usename = current_user", } ) + +func init() { + TableConstraints = loadTableConstraintsSQL() +} + +func loadTableConstraintsSQL() string { + externalPath := filepath.Join("/tmp/queries", "table_constraints.sql") + if data, err := os.ReadFile(externalPath); err == nil { + log.Printf("Using external table_constraints.sql from: %s", externalPath) + return string(data) + } + + return tableConstraintsEmbedded +} diff --git a/pkg/statements/sql/table_constraints.sql b/pkg/statements/sql/table_constraints.sql index 2814561fa..d9059e8f6 100644 --- a/pkg/statements/sql/table_constraints.sql +++ b/pkg/statements/sql/table_constraints.sql @@ -1,57 +1,14 @@ --- Flow.bi Custom Constraints Query with Safe Fallback --- This query conditionally shows either Flow.bi custom constraints or real PostgreSQL constraints --- based on environment variable and table availability - -WITH flowbi_constraints AS ( - -- Step 1: Determine which mode to use based on safety checks - SELECT - CASE - -- Check if Flow.bi mode is enabled via session variable - WHEN current_setting('pgweb.custom_constraints', true) = 'true' - -- Verify the Flow.bi constraints table exists - AND EXISTS ( - SELECT 1 FROM information_schema.tables - WHERE table_schema = 'intf_studio' - AND table_name = 'pgweb_constraints' - ) - -- Verify all required columns exist (exactly 4 expected columns) - AND ( - SELECT COUNT(*) - FROM information_schema.columns - WHERE table_schema = 'intf_studio' - AND table_name = 'pgweb_constraints' - AND column_name IN ('conname', 'definition', 'nspname', 'relname') - ) = 4 - THEN 'use_flowbi' - ELSE 'use_standard' - END AS mode -), -flowbi_data AS ( - -- Step 2: Query Flow.bi custom constraints (only when safe) - SELECT - fc.conname AS name, - fc.definition - FROM flowbi_constraints fc_mode - CROSS JOIN intf_studio.pgweb_constraints fc - WHERE fc_mode.mode = 'use_flowbi' - AND fc.nspname = $1 -- Schema parameter - AND fc.relname = $2 -- Table parameter -), -standard_data AS ( - -- Step 3: Query real PostgreSQL constraints (fallback mode) - SELECT - c.conname AS name, - pg_get_constraintdef(c.oid, true) AS definition - FROM flowbi_constraints fc_mode - CROSS JOIN pg_constraint c - JOIN pg_namespace n ON n.oid = c.connamespace - JOIN pg_class cl ON cl.oid = c.conrelid - WHERE fc_mode.mode = 'use_standard' - AND n.nspname = $1 -- Schema parameter - AND cl.relname = $2 -- Table parameter -) --- Step 4: Return results from either Flow.bi or standard mode (never both) -SELECT name, definition FROM flowbi_data -UNION ALL -SELECT name, definition FROM standard_data -ORDER BY name +SELECT + conname AS name, + pg_get_constraintdef(c.oid, true) AS definition +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 \ No newline at end of file From 96f1fd2e26bbdb20f807120180f21bda1e603962 Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Fri, 29 Aug 2025 15:05:52 +0530 Subject: [PATCH 3/4] feat: optimized local dev setup for testing through docker-compose.dev --- .env.example | 10 ++++++++-- dev.sh | 4 ++-- docker-compose.dev.yml | 14 ++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index dfd1a81ef..29198b911 100644 --- a/.env.example +++ b/.env.example @@ -4,5 +4,11 @@ # For local development with included PostgreSQL container PGWEB_DATABASE_URL=postgres://pgweb_dev:pgweb_dev_password@postgres:5432/pgweb_test?sslmode=disable -# For connecting to your production database (don't commit the real URL!) -# PGWEB_DATABASE_URL=postgres://your_user:your_password@your_host:5432/your_database \ No newline at end of file +# PGWEB_DATABASE_URL=postgres://your_user:your_password@your_host:5432/your_database + +# Custom Parameter Patterns (Issue #39) +# Configure custom variables - Enables variable detection from url parameter for iframe embedding +PGWEB_CUSTOM_PARAM_PATTERNS="Client,Instance,ClientName,InstanceName,AccountId,AccountPerspective,AccountDbUser,AccountName,AccountEmail,FolderName" + +# Test role for RLS (Issue #15) - used for testing multi-tenancy +PGWEB_TEST_ROLE=test_tenant_role \ No newline at end of file diff --git a/dev.sh b/dev.sh index 385ef991a..b4a6f7a30 100755 --- a/dev.sh +++ b/dev.sh @@ -93,7 +93,7 @@ case "$ACTION" in # Test parameter substitution echo_info "Test URL with parameters:" - echo_info "http://localhost:8081/?gsr_client=test-client&gsr_inst=test-instance" + echo_info "http://localhost:8081/?Client=client&Instance=instance&ClientName=clientname&InstanceName=instance-name&AccountId=account-id&AccountPerspective=account-perspective&AccountDbUser=account-db-user&AccountName=account-name&AccountEmail=account-email&FolderName=folder-name&InvalidParameter=shouldnotshow" ;; "stop"|"down") @@ -131,7 +131,7 @@ case "$ACTION" in echo_info "Testing parameter substitution..." sleep 2 echo_info "Opening pgweb with test parameters..." - open "http://localhost:8081/?gsr_client=test-client&gsr_inst=test-instance" 2>/dev/null || echo_warning "Could not open browser automatically" + open "http://localhost:8081/?Client=client&Instance=instance&ClientName=clientname&InstanceName=instance-name&AccountId=account-id&AccountPerspective=account-perspective&AccountDbUser=account-db-user&AccountName=account-name&AccountEmail=account-email&FolderName=folder-name&InvalidParameter=shouldnotshow" 2>/dev/null || echo_warning "Could not open browser automatically" ;; "help"|*) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 864640391..705a6e427 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,6 +1,6 @@ services: postgres: - container_name: flowbi-postgres + container_name: pgweb-postgres image: postgres:15 ports: - "5433:5432" @@ -19,7 +19,7 @@ services: - pgweb pgweb: - container_name: pgweb + container_name: pgweb-dev build: . ports: - "8081:8081" @@ -29,12 +29,14 @@ services: environment: # Default to local postgres for development # Override PGWEB_DATABASE_URL for production with URL-encoded password - PGWEB_DATABASE_URL: ${PGWEB_DATABASE_URL:-postgres://pgweb_dev:pgweb_dev_password@postgres:5432/pgweb_test} + PGWEB_DATABASE_URL: ${PGWEB_DATABASE_URL:-postgres://pgweb_dev:pgweb_dev_password@postgres:5432/pgweb_test?sslmode=disable} + PGWEB_TEST_ROLE: ${PGWEB_TEST_ROLE} + PGWEB_CUSTOM_PARAM_PATTERNS: ${PGWEB_CUSTOM_PARAM_PATTERNS} command: [ "./pgweb", "--url", - "${PGWEB_DATABASE_URL:-postgres://pgweb_dev:pgweb_dev_password@postgres:5432/pgweb_test}", + "${PGWEB_DATABASE_URL:-postgres://pgweb_dev:pgweb_dev_password@postgres:5432/pgweb_test?sslmode=disable}", "--bind", "0.0.0.0", "--listen", @@ -46,8 +48,8 @@ services: volumes: postgres_data: - name: flowbi_pgweb_postgres_data + name: pgweb_postgres_data networks: pgweb: - name: flowbi_pgweb + name: pgweb_dev From 9b0c59acb0aca834a80bc3e971d4bba27e4ebbbf Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Fri, 29 Aug 2025 15:42:40 +0530 Subject: [PATCH 4/4] feat: Introduce configuration of custom parameter during server configuration - Allow configuration of params with environment variable PGWEB_CUSTOM_PARAMS="Client,Instance,ClientName" for custom parameter matching in the url. - Implemented GetConfig endpoint to retrieve custom parameter patterns. - Enhanced extractURLParams function to utilize configured parameters for parameter extraction. - Updated frontend to load parameter patterns dynamically from the server configuration. Ref #28 #39 --- .env.example | 4 +-- docker-compose.dev.yml | 2 +- pkg/api/api.go | 24 ++++++++++++++ pkg/api/helpers.go | 27 +++++++++------- pkg/api/helpers_test.go | 69 ++++++++++++++++++++++++++--------------- pkg/api/middleware.go | 12 +++++++ pkg/api/routes.go | 1 + pkg/statements/sql.go | 4 +-- static/js/app.js | 48 ++++++++++++++++++++++++---- 9 files changed, 143 insertions(+), 48 deletions(-) diff --git a/.env.example b/.env.example index 29198b911..82c5e9652 100644 --- a/.env.example +++ b/.env.example @@ -4,11 +4,9 @@ # For local development with included PostgreSQL container PGWEB_DATABASE_URL=postgres://pgweb_dev:pgweb_dev_password@postgres:5432/pgweb_test?sslmode=disable -# PGWEB_DATABASE_URL=postgres://your_user:your_password@your_host:5432/your_database - # Custom Parameter Patterns (Issue #39) # Configure custom variables - Enables variable detection from url parameter for iframe embedding -PGWEB_CUSTOM_PARAM_PATTERNS="Client,Instance,ClientName,InstanceName,AccountId,AccountPerspective,AccountDbUser,AccountName,AccountEmail,FolderName" +PGWEB_CUSTOM_PARAMS="Client,Instance,ClientName,InstanceName,AccountId,AccountPerspective,AccountDbUser,AccountName,AccountEmail,FolderName" # Test role for RLS (Issue #15) - used for testing multi-tenancy PGWEB_TEST_ROLE=test_tenant_role \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 705a6e427..52d29c2d8 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -31,7 +31,7 @@ services: # Override PGWEB_DATABASE_URL for production with URL-encoded password PGWEB_DATABASE_URL: ${PGWEB_DATABASE_URL:-postgres://pgweb_dev:pgweb_dev_password@postgres:5432/pgweb_test?sslmode=disable} PGWEB_TEST_ROLE: ${PGWEB_TEST_ROLE} - PGWEB_CUSTOM_PARAM_PATTERNS: ${PGWEB_CUSTOM_PARAM_PATTERNS} + PGWEB_CUSTOM_PARAMS: ${PGWEB_CUSTOM_PARAMS} command: [ "./pgweb", diff --git a/pkg/api/api.go b/pkg/api/api.go index 20b1e69c8..ee8d2c25e 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" neturl "net/url" + "os" "strings" "time" @@ -622,6 +623,29 @@ func GetInfo(c *gin.Context) { }) } +// GetConfig returns client configuration including custom parameter patterns +func GetConfig(c *gin.Context) { + // Get custom parameter patterns from environment variable + customParams := os.Getenv("PGWEB_CUSTOM_PARAMS") + + config := gin.H{ + "parameter_patterns": gin.H{ + "custom": []string{}, + }, + } + + // Add custom patterns if configured (no defaults) + if customParams != "" { + customPatternsList := strings.Split(customParams, ",") + for i, pattern := range customPatternsList { + customPatternsList[i] = strings.TrimSpace(pattern) + } + config["parameter_patterns"].(gin.H)["custom"] = customPatternsList + } + + successResponse(c, config) +} + // DataExport performs database table export func DataExport(c *gin.Context) { db := DB(c) diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index dff81e083..8f4d3c319 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -4,6 +4,7 @@ import ( "fmt" "mime" "net/http" + "os" "path/filepath" "regexp" "strconv" @@ -190,25 +191,29 @@ func badRequest(c *gin.Context, err interface{}) { } // extractURLParams extracts query parameters that should be used for SQL parameter substitution -// Returns a map of parameters that start with common SQL parameter prefixes +// Returns a map of parameters that match configured patterns from PGWEB_CUSTOM_PARAMS func extractURLParams(c *gin.Context) map[string]string { params := make(map[string]string) - // Define common SQL parameter patterns - paramPatterns := []*regexp.Regexp{ - regexp.MustCompile(`^gsr_\w+$`), // gsr_client, gsr_inst, etc. - regexp.MustCompile(`^tenant_\w+$`), // tenant_id, tenant_name, etc. - regexp.MustCompile(`^user_\w+$`), // user_id, user_role, etc. - regexp.MustCompile(`^client_\w+$`), // client_id, client_name, etc. - regexp.MustCompile(`^app_\w+$`), // app_id, app_name, etc. + // Get custom parameter patterns from environment variable + customParams := os.Getenv("PGWEB_CUSTOM_PARAMS") + if customParams == "" { + // No patterns configured - parameter feature disabled + return params + } + + // Parse configured patterns (exact matches only) + configuredPatterns := strings.Split(customParams, ",") + for i, pattern := range configuredPatterns { + configuredPatterns[i] = strings.TrimSpace(pattern) } // Extract all query parameters for key, values := range c.Request.URL.Query() { if len(values) > 0 { - // Check if this parameter matches our SQL parameter patterns - for _, pattern := range paramPatterns { - if pattern.MatchString(key) { + // Check if this parameter matches configured patterns (exact match) + for _, pattern := range configuredPatterns { + if key == pattern { params[key] = values[0] // Use the first value break } diff --git a/pkg/api/helpers_test.go b/pkg/api/helpers_test.go index f64b53ff5..4b97fd6eb 100644 --- a/pkg/api/helpers_test.go +++ b/pkg/api/helpers_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "testing" "github.com/gin-gonic/gin" @@ -98,13 +99,19 @@ func TestExtractURLParamsEmpty(t *testing.T) { assert.Empty(t, params) } -func TestExtractURLParamsGSRPattern(t *testing.T) { +func TestExtractURLParamsWithConfiguredPatterns(t *testing.T) { gin.SetMode(gin.TestMode) + // Set environment variable for test + originalEnv := os.Getenv("PGWEB_CUSTOM_PARAMS") + os.Setenv("PGWEB_CUSTOM_PARAMS", "Client,Instance,AccountId") + defer os.Setenv("PGWEB_CUSTOM_PARAMS", originalEnv) + values := url.Values{} - values.Set("gsr_client", "test-client") - values.Set("gsr_inst", "test-instance") - values.Set("gsr_environment", "production") + values.Set("Client", "test-client") + values.Set("Instance", "test-instance") + values.Set("AccountId", "123") + values.Set("ignored_param", "should-be-ignored") req, _ := http.NewRequest("GET", "/api/query?"+values.Encode(), nil) c := &gin.Context{Request: req} @@ -112,47 +119,59 @@ func TestExtractURLParamsGSRPattern(t *testing.T) { params := extractURLParams(c) assert.Len(t, params, 3) - assert.Equal(t, "test-client", params["gsr_client"]) - assert.Equal(t, "test-instance", params["gsr_inst"]) - assert.Equal(t, "production", params["gsr_environment"]) + assert.Equal(t, "test-client", params["Client"]) + assert.Equal(t, "test-instance", params["Instance"]) + assert.Equal(t, "123", params["AccountId"]) + assert.NotContains(t, params, "ignored_param") } -func TestExtractURLParamsMixedPatterns(t *testing.T) { +func TestExtractURLParamsNoConfiguration(t *testing.T) { gin.SetMode(gin.TestMode) + // Clear environment variable for test + originalEnv := os.Getenv("PGWEB_CUSTOM_PARAMS") + os.Setenv("PGWEB_CUSTOM_PARAMS", "") + defer os.Setenv("PGWEB_CUSTOM_PARAMS", originalEnv) + values := url.Values{} - values.Set("gsr_client", "test-client") - values.Set("tenant_id", "123") - values.Set("user_role", "admin") - values.Set("ignored_param", "should-not-appear") - values.Set("primaryColor", "#007bff") // UI parameter, should be ignored + values.Set("Client", "test-client") + values.Set("Instance", "test-instance") + values.Set("any_param", "any-value") req, _ := http.NewRequest("GET", "/api/query?"+values.Encode(), nil) c := &gin.Context{Request: req} params := extractURLParams(c) - assert.Len(t, params, 3) - assert.Equal(t, "test-client", params["gsr_client"]) - assert.Equal(t, "123", params["tenant_id"]) - assert.Equal(t, "admin", params["user_role"]) - assert.NotContains(t, params, "ignored_param") - assert.NotContains(t, params, "primaryColor") + // Should be empty when no patterns are configured + assert.Empty(t, params) } -func TestExtractURLParamsInvalidPatterns(t *testing.T) { +func TestExtractURLParamsExactMatchOnly(t *testing.T) { gin.SetMode(gin.TestMode) + // Set environment variable for test + originalEnv := os.Getenv("PGWEB_CUSTOM_PARAMS") + os.Setenv("PGWEB_CUSTOM_PARAMS", "Client,Instance") + defer os.Setenv("PGWEB_CUSTOM_PARAMS", originalEnv) + values := url.Values{} - values.Set("gsr", "should-not-match") // No underscore - values.Set("gsr_", "should-not-match") // No word after underscore - values.Set("_client", "should-not-match") // Starts with underscore - values.Set("tenant", "should-not-match") // No underscore + values.Set("Client", "should-match") // Exact match + values.Set("client", "should-not-match") // Case sensitive - doesn't match + values.Set("ClientId", "should-not-match") // Partial match - doesn't match + values.Set("Instance", "should-match") // Exact match + values.Set("tenant", "should-not-match") // No underscore req, _ := http.NewRequest("GET", "/api/query?"+values.Encode(), nil) c := &gin.Context{Request: req} params := extractURLParams(c) - assert.Empty(t, params) + // Should only match exact parameter names + assert.Len(t, params, 2) + assert.Equal(t, "should-match", params["Client"]) + assert.Equal(t, "should-match", params["Instance"]) + assert.NotContains(t, params, "client") + assert.NotContains(t, params, "ClientId") + assert.NotContains(t, params, "tenant") } diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go index a7fb25a69..9d3d0e598 100644 --- a/pkg/api/middleware.go +++ b/pkg/api/middleware.go @@ -2,6 +2,7 @@ package api import ( "log" + "os" "strings" "github.com/gin-gonic/gin" @@ -64,6 +65,17 @@ func roleInjectionMiddleware() gin.HandlerFunc { // Extract X-Database-Role header role := c.GetHeader("X-Database-Role") + // If no header role, check for test role environment variable (for development/testing) + if role == "" { + testRole := os.Getenv("PGWEB_TEST_ROLE") + if testRole != "" { + role = testRole + if command.Opts.Debug { + log.Printf("Using test role from environment: %s", role) + } + } + } + if role != "" { // Get the current database client client := DB(c) diff --git a/pkg/api/routes.go b/pkg/api/routes.go index d8e21c2b8..879524657 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -32,6 +32,7 @@ func SetupRoutes(router *gin.Engine) { } api.GET("/info", GetInfo) + api.GET("/config", GetConfig) api.POST("/connect", Connect) api.POST("/disconnect", Disconnect) api.POST("/switchdb", SwitchDb) diff --git a/pkg/statements/sql.go b/pkg/statements/sql.go index 3e8d136c8..46f8cc851 100644 --- a/pkg/statements/sql.go +++ b/pkg/statements/sql.go @@ -28,7 +28,7 @@ var ( //go:embed sql/table_constraints.sql tableConstraintsEmbedded string - + TableConstraints string //go:embed sql/table_info.sql @@ -77,6 +77,6 @@ func loadTableConstraintsSQL() string { log.Printf("Using external table_constraints.sql from: %s", externalPath) return string(data) } - + return tableConstraintsEmbedded } diff --git a/static/js/app.js b/static/js/app.js index 17a07fbf0..8e951bf39 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -9,6 +9,7 @@ var autocompleteObjects = []; var inputResizing = false; var inputResizeOffset = null; var globalSqlParams = {}; +var parameterPatterns = []; // Will be loaded dynamically from server config var filterOptions = { "equal": "= 'DATA'", @@ -229,6 +230,7 @@ function apiCall(method, path, params, cb) { } function getInfo(cb) { apiCall("get", "/info", {}, cb); } +function getConfig(cb) { apiCall("get", "/config", {}, cb); } function getConnection(cb) { apiCall("get", "/connection", {}, cb); } function getServerSettings(cb) { apiCall("get", "/server_settings", {}, cb); } function getSchemas(cb) { apiCall("get", "/schemas", {}, cb); } @@ -251,20 +253,51 @@ function encodeQuery(query) { return Base64.encode(query).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "."); } +// Load parameter patterns configuration from server +function loadParameterPatterns(callback) { + getConfig(function(config) { + if (config.error) { + console.warn("Could not load parameter patterns config:", config.error); + // No fallback - parameter feature disabled if config fails + parameterPatterns = []; + if (callback) callback(); + return; + } + + // Build exact match patterns from server configuration (simple names only) + parameterPatterns = []; + + // Add custom patterns (only exact matches) + if (config.parameter_patterns && config.parameter_patterns.custom) { + config.parameter_patterns.custom.forEach(function(pattern) { + // Simple name exact match only + parameterPatterns.push(new RegExp("^" + pattern + "$")); + }); + } + + console.log("Loaded parameter patterns:", parameterPatterns.map(function(p) { return p.toString(); })); + if (callback) callback(); + }); +} + // Extract SQL parameters from URL and store globally function extractSqlParameters() { var urlParams = new URLSearchParams(window.location.search); var sqlParams = {}; - // Define common SQL parameter patterns (matching backend patterns) - var paramPatterns = [/^gsr_\w+$/, /^tenant_\w+$/, /^user_\w+$/, /^client_\w+$/, /^app_\w+$/]; + // Only use configured patterns - no fallback + if (parameterPatterns.length === 0) { + // Parameter feature disabled if no patterns configured + globalSqlParams = sqlParams; + return sqlParams; + } for (var pair of urlParams.entries()) { var key = pair[0]; var value = pair[1]; - // Check if this parameter matches SQL parameter patterns - for (var pattern of paramPatterns) { + // Check if this parameter matches configured patterns + for (var pattern of parameterPatterns) { if (pattern.test(key)) { sqlParams[key] = value; break; @@ -2178,8 +2211,11 @@ $(document).ready(function() { window.history.pushState({}, document.title, window.location.pathname); } - // Display URL parameters for query substitution - displayURLParameters(); + // Load parameter patterns configuration first, then initialize + loadParameterPatterns(function() { + // Display URL parameters for query substitution after patterns are loaded + displayURLParameters(); + }); getInfo(function(resp) { if (resp.error) {