mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-09 07:28:05 -05:00
feat(tools/postgres)!: Add additional filter params for existing postgres tools (#2033)
## Description Add additional filter parameters for existing PostgreSQL tools: 1. `list_views`: - Add a new optional `"schema_name"` filter parameter to return results based on a specific schema name pattern. - Add an additional column `"definition"` to return the view definition. 2. `list_schemas`: - Add a new optional `"owner"` filter parameter to return results based on a specific owner name pattern. - Add a new optional `"limit"` parameter to return a specific number of rows. 3. `list_indexes`: - Add a new optional `"only_unused"` filter parameter to return only unused indexes. > Should include a concise description of the changes (bug or feature), it's > impact, along with a summary of the solution list_views <img width="1531" height="763" alt="Screenshot 2025-11-25 at 1 36 39 PM" src="https://github.com/user-attachments/assets/bd6805b3-43d2-46c7-adc8-62d3a4521d36" /> list_schemas <img width="1519" height="755" alt="Screenshot 2025-11-25 at 1 35 54 PM" src="https://github.com/user-attachments/assets/62d3e987-b64e-442b-ba1a-84def1df7a58" /> list_indexes <img width="1523" height="774" alt="Screenshot 2025-11-25 at 1 35 32 PM" src="https://github.com/user-attachments/assets/c6f73b3f-f8a2-4b76-9218-64d7011a2241" /> ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [x] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) - [x] Make sure to add `!` if this involve a breaking change 🛠️ Fixes #<1738> Co-authored-by: Averi Kitsch <akitsch@google.com>
This commit is contained in:
@@ -21,12 +21,10 @@ any of the following sources:
|
||||
`postgres-list-indexes` lists detailed information as JSON for indexes. The tool
|
||||
takes the following input parameters:
|
||||
|
||||
- `table_name` (optional): A text to filter results by table name. The input is
|
||||
used within a LIKE clause. Default: `""`
|
||||
- `index_name` (optional): A text to filter results by index name. The input is
|
||||
used within a LIKE clause. Default: `""`
|
||||
- `schema_name` (optional): A text to filter results by schema name. The input
|
||||
is used within a LIKE clause. Default: `""`
|
||||
- `table_name` (optional): A text to filter results by table name. Default: `""`
|
||||
- `index_name` (optional): A text to filter results by index name. Default: `""`
|
||||
- `schema_name` (optional): A text to filter results by schema name. Default: `""`
|
||||
- `only_unused` (optional): If true, returns indexes that have never been used.
|
||||
- `limit` (optional): The maximum number of rows to return. Default: `50`.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -21,9 +21,9 @@ the following sources:
|
||||
`postgres-list-schemas` lists detailed information as JSON for each schema. The
|
||||
tool takes the following input parameters:
|
||||
|
||||
- `schema_name` (optional): A pattern to filter schema names using SQL LIKE
|
||||
operator.
|
||||
If omitted, all user-defined schemas are returned.
|
||||
- `schema_name` (optional): A text to filter results by schema name. Default: `""`
|
||||
- `owner` (optional): A text to filter results by owner name. Default: `""`
|
||||
- `limit` (optional): The maximum number of rows to return. Default: `50`.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Postgres database. It's compatible with any of the following sources:
|
||||
`postgres-list-sequences` lists detailed information as JSON for all sequences.
|
||||
The tool takes the following input parameters:
|
||||
|
||||
- `sequencename` (optional): A text to filter results by sequence name. The
|
||||
- `sequence_name` (optional): A text to filter results by sequence name. The
|
||||
input is used within a LIKE clause. Default: `""`
|
||||
- `schemaname` (optional): A text to filter results by schema name. The input is
|
||||
- `schema_name` (optional): A text to filter results by schema name. The input is
|
||||
used within a LIKE clause. Default: `""`
|
||||
- `limit` (optional): The maximum number of rows to return. Default: `50`.
|
||||
|
||||
@@ -45,9 +45,9 @@ The response is a json array with the following elements:
|
||||
|
||||
```json
|
||||
{
|
||||
"sequencename": "sequence name",
|
||||
"schemaname": "schema name",
|
||||
"sequenceowner": "owner of the sequence",
|
||||
"sequence_name": "sequence name",
|
||||
"schema_name": "schema name",
|
||||
"sequence_owner": "owner of the sequence",
|
||||
"data_type": "data type of the sequence",
|
||||
"start_value": "starting value of the sequence",
|
||||
"min_value": "minimum value of the sequence",
|
||||
|
||||
@@ -19,11 +19,11 @@ a Postgres database, excluding those in system schemas (`pg_catalog`,
|
||||
- [postgres](../../sources/postgres.md)
|
||||
|
||||
`postgres-list-views` lists detailed view information (schemaname, viewname,
|
||||
ownername) as JSON for views in a database. The tool takes the following input
|
||||
ownername, definition) as JSON for views in a database. The tool takes the following input
|
||||
parameters:
|
||||
|
||||
- `viewname` (optional): A string pattern to filter view names. The search uses
|
||||
SQL LIKE operator to filter the views. Default: `""`
|
||||
- `view_name` (optional): A string pattern to filter view names. Default: `""`
|
||||
- `schema_name` (optional): A string pattern to filter schema names. Default: `""`
|
||||
- `limit` (optional): The maximum number of rows to return. Default: `50`.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -98,11 +98,10 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
||||
}
|
||||
|
||||
allParameters := parameters.Parameters{}
|
||||
description := cfg.Description
|
||||
if description == "" {
|
||||
description = "Fetches the current state of the PostgreSQL server, returning the version, whether it's a replica, uptime duration, maximum connection limit, number of current connections, number of active connections, and the percentage of connections in use."
|
||||
if cfg.Description == "" {
|
||||
cfg.Description = "Fetches the current state of the PostgreSQL server, returning the version, whether it's a replica, uptime duration, maximum connection limit, number of current connections, number of active connections, and the percentage of connections in use."
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters, nil)
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters, nil)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
@@ -134,7 +133,13 @@ func (t Tool) ToConfig() tools.ToolConfig {
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
sliceParams := params.AsSlice()
|
||||
paramsMap := params.AsMap()
|
||||
|
||||
newParams, err := parameters.GetParams(t.allParams, paramsMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract standard params %w", err)
|
||||
}
|
||||
sliceParams := newParams.AsSlice()
|
||||
|
||||
results, err := t.pool.Query(ctx, databaseOverviewStatement, sliceParams...)
|
||||
if err != nil {
|
||||
|
||||
@@ -59,7 +59,8 @@ const listIndexesStatement = `
|
||||
ON i.oid = s.indexrelid
|
||||
WHERE
|
||||
t.relkind = 'r'
|
||||
AND s.schemaname NOT IN ('pg_catalog', 'information_schema')
|
||||
AND s.schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
AND s.schemaname NOT LIKE 'pg_temp_%'
|
||||
)
|
||||
SELECT *
|
||||
FROM IndexDetails
|
||||
@@ -67,11 +68,12 @@ const listIndexesStatement = `
|
||||
($1::text IS NULL OR schema_name LIKE '%' || $1 || '%')
|
||||
AND ($2::text IS NULL OR table_name LIKE '%' || $2 || '%')
|
||||
AND ($3::text IS NULL OR index_name LIKE '%' || $3 || '%')
|
||||
AND ($4::boolean IS NOT TRUE OR is_used IS FALSE)
|
||||
ORDER BY
|
||||
schema_name,
|
||||
table_name,
|
||||
index_name
|
||||
LIMIT COALESCE($4::int, 50);
|
||||
LIMIT COALESCE($5::int, 50);
|
||||
`
|
||||
|
||||
func init() {
|
||||
@@ -131,13 +133,14 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
||||
parameters.NewStringParameterWithDefault("schema_name", "", "Optional: a text to filter results by schema name. The input is used within a LIKE clause."),
|
||||
parameters.NewStringParameterWithDefault("table_name", "", "Optional: a text to filter results by table name. The input is used within a LIKE clause."),
|
||||
parameters.NewStringParameterWithDefault("index_name", "", "Optional: a text to filter results by index name. The input is used within a LIKE clause."),
|
||||
parameters.NewBooleanParameterWithDefault("only_unused", false, "Optional: If true, only returns indexes that have never been used."),
|
||||
parameters.NewIntParameterWithDefault("limit", 50, "Optional: The maximum number of rows to return. Default is 50"),
|
||||
}
|
||||
description := cfg.Description
|
||||
if description == "" {
|
||||
description = "Lists available user indexes in the database, excluding system schemas (pg_catalog, information_schema). For each index, the following properties are returned: schema name, table name, index name, index type (access method), a boolean indicating if it's a unique index, a boolean indicating if it's for a primary key, the index definition, index size in bytes, the number of index scans, the number of index tuples read, the number of table tuples fetched via index scans, and a boolean indicating if the index has been used at least once."
|
||||
|
||||
if cfg.Description == "" {
|
||||
cfg.Description = "Lists available user indexes in the database, excluding system schemas (pg_catalog, information_schema). For each index, the following properties are returned: schema name, table name, index name, index type (access method), a boolean indicating if it's a unique index, a boolean indicating if it's for a primary key, the index definition, index size in bytes, the number of index scans, the number of index tuples read, the number of table tuples fetched via index scans, and a boolean indicating if the index has been used at least once."
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters, nil)
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters, nil)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
@@ -169,7 +172,13 @@ func (t Tool) ToConfig() tools.ToolConfig {
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
sliceParams := params.AsSlice()
|
||||
paramsMap := params.AsMap()
|
||||
|
||||
newParams, err := parameters.GetParams(t.allParams, paramsMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract standard params %w", err)
|
||||
}
|
||||
sliceParams := newParams.AsSlice()
|
||||
|
||||
results, err := t.pool.Query(ctx, listIndexesStatement, sliceParams...)
|
||||
if err != nil {
|
||||
|
||||
@@ -32,28 +32,28 @@ const kind string = "postgres-list-schemas"
|
||||
|
||||
const listSchemasStatement = `
|
||||
WITH
|
||||
schema_grants AS (
|
||||
SELECT schema_oid, jsonb_object_agg(grantee, privileges) AS grants
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
n.oid AS schema_oid,
|
||||
CASE
|
||||
WHEN p.grantee = 0 THEN 'PUBLIC'
|
||||
ELSE pg_catalog.pg_get_userbyid(p.grantee)
|
||||
END
|
||||
AS grantee,
|
||||
jsonb_agg(p.privilege_type ORDER BY p.privilege_type) AS privileges
|
||||
FROM pg_catalog.pg_namespace n, aclexplode(n.nspacl) p
|
||||
WHERE n.nspacl IS NOT NULL
|
||||
GROUP BY n.oid, grantee
|
||||
) permissions_by_grantee
|
||||
GROUP BY schema_oid
|
||||
),
|
||||
all_schemas AS (
|
||||
SELECT
|
||||
n.nspname AS schema_name,
|
||||
pg_catalog.pg_get_userbyid(n.nspowner) AS owner,
|
||||
schema_grants AS (
|
||||
SELECT schema_oid, jsonb_object_agg(grantee, privileges) AS grants
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
n.oid AS schema_oid,
|
||||
CASE
|
||||
WHEN p.grantee = 0 THEN 'PUBLIC'
|
||||
ELSE pg_catalog.pg_get_userbyid(p.grantee)
|
||||
END
|
||||
AS grantee,
|
||||
jsonb_agg(p.privilege_type ORDER BY p.privilege_type) AS privileges
|
||||
FROM pg_catalog.pg_namespace n, aclexplode(n.nspacl) p
|
||||
WHERE n.nspacl IS NOT NULL
|
||||
GROUP BY n.oid, grantee
|
||||
) permissions_by_grantee
|
||||
GROUP BY schema_oid
|
||||
),
|
||||
all_schemas AS (
|
||||
SELECT
|
||||
n.nspname AS schema_name,
|
||||
pg_catalog.pg_get_userbyid(n.nspowner) AS owner,
|
||||
COALESCE(sg.grants, '{}'::jsonb) AS grants,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
@@ -67,18 +67,21 @@ const listSchemasStatement = `
|
||||
) AS views,
|
||||
(SELECT COUNT(*) FROM pg_catalog.pg_proc p WHERE p.pronamespace = n.oid)
|
||||
AS functions
|
||||
FROM pg_catalog.pg_namespace n
|
||||
LEFT JOIN schema_grants sg
|
||||
ON n.oid = sg.schema_oid
|
||||
)
|
||||
FROM pg_catalog.pg_namespace n
|
||||
LEFT JOIN schema_grants sg
|
||||
ON n.oid = sg.schema_oid
|
||||
)
|
||||
SELECT *
|
||||
FROM all_schemas
|
||||
-- Exclude system schemas and temporary schemas created per session.
|
||||
-- Exclude system schemas and temporary schemas created per session.
|
||||
WHERE
|
||||
schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
AND schema_name NOT LIKE 'pg_temp_%'
|
||||
AND ($1::text IS NULL OR schema_name LIKE '%' || $1::text || '%')
|
||||
ORDER BY schema_name;
|
||||
schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
AND schema_name NOT LIKE 'pg_temp_%'
|
||||
AND schema_name NOT LIKE 'pg_toast_temp_%'
|
||||
AND ($1::text IS NULL OR schema_name ILIKE '%' || $1::text || '%')
|
||||
AND ($2::text IS NULL OR owner ILIKE '%' || $2::text || '%')
|
||||
ORDER BY schema_name
|
||||
LIMIT COALESCE($3::int, NULL);
|
||||
`
|
||||
|
||||
func init() {
|
||||
@@ -136,12 +139,14 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
||||
|
||||
allParameters := parameters.Parameters{
|
||||
parameters.NewStringParameterWithDefault("schema_name", "", "Optional: A specific schema name pattern to search for."),
|
||||
parameters.NewStringParameterWithDefault("owner", "", "Optional: A specific schema owner name pattern to search for."),
|
||||
parameters.NewIntParameterWithDefault("limit", 10, "Optional: The maximum number of schemas to return."),
|
||||
}
|
||||
description := cfg.Description
|
||||
if description == "" {
|
||||
description = "Lists all schemas in the database ordered by schema name and excluding system and temporary schemas. It returns the schema name, schema owner, grants, number of functions, number of tables and number of views within each schema."
|
||||
|
||||
if cfg.Description == "" {
|
||||
cfg.Description = "Lists all schemas in the database ordered by schema name and excluding system and temporary schemas. It returns the schema name, schema owner, grants, number of functions, number of tables and number of views within each schema."
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters, nil)
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters, nil)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
@@ -169,7 +174,13 @@ type Tool struct {
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
sliceParams := params.AsSlice()
|
||||
paramsMap := params.AsMap()
|
||||
|
||||
newParams, err := parameters.GetParams(t.allParams, paramsMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract standard params %w", err)
|
||||
}
|
||||
sliceParams := newParams.AsSlice()
|
||||
|
||||
results, err := t.pool.Query(ctx, listSchemasStatement, sliceParams...)
|
||||
if err != nil {
|
||||
|
||||
@@ -32,9 +32,9 @@ const kind string = "postgres-list-sequences"
|
||||
|
||||
const listSequencesStatement = `
|
||||
SELECT
|
||||
sequencename,
|
||||
schemaname,
|
||||
sequenceowner,
|
||||
sequencename as sequence_name,
|
||||
schemaname as schema_name,
|
||||
sequenceowner as sequence_owner,
|
||||
data_type,
|
||||
start_value,
|
||||
min_value,
|
||||
@@ -45,7 +45,7 @@ const listSequencesStatement = `
|
||||
WHERE
|
||||
($1::text IS NULL OR schemaname LIKE '%' || $1 || '%')
|
||||
AND ($2::text IS NULL OR sequencename LIKE '%' || $2 || '%')
|
||||
ORDER BY schemaname, sequencename
|
||||
ORDER BY schema_name, sequence_name
|
||||
LIMIT COALESCE($3::int, 50);
|
||||
|
||||
`
|
||||
@@ -104,15 +104,15 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
||||
}
|
||||
|
||||
allParameters := parameters.Parameters{
|
||||
parameters.NewStringParameterWithDefault("schemaname", "", "Optional: A specific schema name pattern to search for."),
|
||||
parameters.NewStringParameterWithDefault("sequencename", "", "Optional: A specific sequence name pattern to search for."),
|
||||
parameters.NewStringParameterWithDefault("schema_name", "", "Optional: A specific schema name pattern to search for."),
|
||||
parameters.NewStringParameterWithDefault("sequence_name", "", "Optional: A specific sequence name pattern to search for."),
|
||||
parameters.NewIntParameterWithDefault("limit", 50, "Optional: The maximum number of rows to return. Default is 50"),
|
||||
}
|
||||
description := cfg.Description
|
||||
if description == "" {
|
||||
description = "Lists sequences in the database. Returns sequence name, schema name, sequence owner, data type of the sequence, starting value, minimum value, maximum value of the sequence, the value by which the sequence is incremented, and the last value generated by the sequence in the current session"
|
||||
|
||||
if cfg.Description == "" {
|
||||
cfg.Description = "Lists sequences in the database. Returns sequence name, schema name, sequence owner, data type of the sequence, starting value, minimum value, maximum value of the sequence, the value by which the sequence is incremented, and the last value generated by the sequence in the current session"
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters, nil)
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters, nil)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
@@ -144,7 +144,13 @@ func (t Tool) ToConfig() tools.ToolConfig {
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
sliceParams := params.AsSlice()
|
||||
paramsMap := params.AsMap()
|
||||
|
||||
newParams, err := parameters.GetParams(t.allParams, paramsMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract standard params %w", err)
|
||||
}
|
||||
sliceParams := newParams.AsSlice()
|
||||
|
||||
results, err := t.pool.Query(ctx, listSequencesStatement, sliceParams...)
|
||||
if err != nil {
|
||||
|
||||
@@ -135,11 +135,11 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
||||
parameters.NewStringParameterWithDefault("table_name", "", "Optional: A specific table name pattern to search for."),
|
||||
parameters.NewIntParameterWithDefault("limit", 50, "Optional: The maximum number of rows to return."),
|
||||
}
|
||||
description := cfg.Description
|
||||
if description == "" {
|
||||
description = "Lists all non-internal triggers in a database. Returns trigger name, schema name, table name, whether its enabled or disabled, timing (e.g BEFORE/AFTER of the event), the events that cause the trigger to fire such as INSERT, UPDATE, or DELETE, whether the trigger activates per ROW or per STATEMENT, the handler function executed by the trigger and full definition."
|
||||
|
||||
if cfg.Description == "" {
|
||||
cfg.Description = "Lists all non-internal triggers in a database. Returns trigger name, schema name, table name, whether its enabled or disabled, timing (e.g BEFORE/AFTER of the event), the events that cause the trigger to fire such as INSERT, UPDATE, or DELETE, whether the trigger activates per ROW or per STATEMENT, the handler function executed by the trigger and full definition."
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters, nil)
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters, nil)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
@@ -171,7 +171,13 @@ func (t Tool) ToConfig() tools.ToolConfig {
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
sliceParams := params.AsSlice()
|
||||
paramsMap := params.AsMap()
|
||||
|
||||
newParams, err := parameters.GetParams(t.allParams, paramsMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract standard params %w", err)
|
||||
}
|
||||
sliceParams := newParams.AsSlice()
|
||||
|
||||
results, err := t.pool.Query(ctx, listTriggersStatement, sliceParams...)
|
||||
if err != nil {
|
||||
|
||||
@@ -31,13 +31,24 @@ import (
|
||||
const kind string = "postgres-list-views"
|
||||
|
||||
const listViewsStatement = `
|
||||
SELECT schemaname, viewname, viewowner
|
||||
FROM pg_views
|
||||
WHERE
|
||||
schemaname NOT IN ('pg_catalog', 'information_schema')
|
||||
AND ($1::text IS NULL OR viewname LIKE '%' || $1::text || '%')
|
||||
ORDER BY viewname
|
||||
LIMIT COALESCE($2::int, 50);
|
||||
WITH list_views AS (
|
||||
SELECT
|
||||
schemaname AS schema_name,
|
||||
viewname AS view_name,
|
||||
viewowner AS owner_name,
|
||||
definition
|
||||
FROM pg_views
|
||||
)
|
||||
SELECT *
|
||||
FROM list_views
|
||||
WHERE
|
||||
schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
||||
AND schema_name NOT LIKE 'pg_temp_%'
|
||||
AND ($1::text IS NULL OR view_name ILIKE '%' || $1::text || '%')
|
||||
AND ($2::text IS NULL OR schema_name ILIKE '%' || $2::text || '%')
|
||||
ORDER BY
|
||||
schema_name, view_name
|
||||
LIMIT COALESCE($3::int, 50);
|
||||
`
|
||||
|
||||
func init() {
|
||||
@@ -94,15 +105,15 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
||||
}
|
||||
|
||||
allParameters := parameters.Parameters{
|
||||
parameters.NewStringParameterWithDefault("viewname", "", "Optional: A specific view name to search for."),
|
||||
parameters.NewStringParameterWithDefault("view_name", "", "Optional: A specific view name to search for."),
|
||||
parameters.NewStringParameterWithDefault("schema_name", "", "Optional: A specific schema name to search for."),
|
||||
parameters.NewIntParameterWithDefault("limit", 50, "Optional: The maximum number of rows to return."),
|
||||
}
|
||||
paramManifest := allParameters.Manifest()
|
||||
description := cfg.Description
|
||||
if description == "" {
|
||||
description = "Lists views in the database from pg_views with a default limit of 50 rows. Returns schemaname, viewname and the ownername."
|
||||
if cfg.Description == "" {
|
||||
cfg.Description = "Lists views in the database from pg_views with a default limit of 50 rows. Returns schemaname, viewname, ownername and the definition."
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters, nil)
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters, nil)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestAlloyDBPgToolEndpoints(t *testing.T) {
|
||||
|
||||
// Run Postgres prebuilt tool tests
|
||||
tests.RunPostgresListTablesTest(t, tableNameParam, tableNameAuth, AlloyDBPostgresUser)
|
||||
tests.RunPostgresListViewsTest(t, ctx, pool, tableNameParam)
|
||||
tests.RunPostgresListViewsTest(t, ctx, pool)
|
||||
tests.RunPostgresListSchemasTest(t, ctx, pool)
|
||||
tests.RunPostgresListActiveQueriesTest(t, ctx, pool)
|
||||
tests.RunPostgresListAvailableExtensionsTest(t)
|
||||
|
||||
@@ -165,7 +165,7 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
|
||||
|
||||
// Run Postgres prebuilt tool tests
|
||||
tests.RunPostgresListTablesTest(t, tableNameParam, tableNameAuth, CloudSQLPostgresUser)
|
||||
tests.RunPostgresListViewsTest(t, ctx, pool, tableNameParam)
|
||||
tests.RunPostgresListViewsTest(t, ctx, pool)
|
||||
tests.RunPostgresListSchemasTest(t, ctx, pool)
|
||||
tests.RunPostgresListActiveQueriesTest(t, ctx, pool)
|
||||
tests.RunPostgresListAvailableExtensionsTest(t)
|
||||
|
||||
@@ -144,7 +144,7 @@ func TestPostgres(t *testing.T) {
|
||||
|
||||
// Run Postgres prebuilt tool tests
|
||||
tests.RunPostgresListTablesTest(t, tableNameParam, tableNameAuth, PostgresUser)
|
||||
tests.RunPostgresListViewsTest(t, ctx, pool, tableNameParam)
|
||||
tests.RunPostgresListViewsTest(t, ctx, pool)
|
||||
tests.RunPostgresListSchemasTest(t, ctx, pool)
|
||||
tests.RunPostgresListActiveQueriesTest(t, ctx, pool)
|
||||
tests.RunPostgresListAvailableExtensionsTest(t)
|
||||
|
||||
@@ -1260,8 +1260,8 @@ func RunPostgresListTablesTest(t *testing.T, tableNameParam, tableNameAuth, user
|
||||
}
|
||||
}
|
||||
|
||||
func setUpPostgresViews(t *testing.T, ctx context.Context, pool *pgxpool.Pool, viewName, tableName string) func() {
|
||||
createView := fmt.Sprintf("CREATE VIEW %s AS SELECT name FROM %s", viewName, tableName)
|
||||
func setUpPostgresViews(t *testing.T, ctx context.Context, pool *pgxpool.Pool, viewName string) func() {
|
||||
createView := fmt.Sprintf("CREATE VIEW %s AS SELECT 1 AS col", viewName)
|
||||
_, err := pool.Exec(ctx, createView)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create view: %v", err)
|
||||
@@ -1275,9 +1275,10 @@ func setUpPostgresViews(t *testing.T, ctx context.Context, pool *pgxpool.Pool, v
|
||||
}
|
||||
}
|
||||
|
||||
func RunPostgresListViewsTest(t *testing.T, ctx context.Context, pool *pgxpool.Pool, tableName string) {
|
||||
viewName1 := "test_view_1" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
dropViewfunc1 := setUpPostgresViews(t, ctx, pool, viewName1, tableName)
|
||||
func RunPostgresListViewsTest(t *testing.T, ctx context.Context, pool *pgxpool.Pool) {
|
||||
//adding this line temporarily
|
||||
viewName := "test_view_" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
dropViewfunc1 := setUpPostgresViews(t, ctx, pool, viewName)
|
||||
defer dropViewfunc1()
|
||||
|
||||
invokeTcs := []struct {
|
||||
@@ -1288,13 +1289,13 @@ func RunPostgresListViewsTest(t *testing.T, ctx context.Context, pool *pgxpool.P
|
||||
}{
|
||||
{
|
||||
name: "invoke list_views with newly created view",
|
||||
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"viewname": "%s"}`, viewName1))),
|
||||
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"view_name": "%s"}`, viewName))),
|
||||
wantStatusCode: http.StatusOK,
|
||||
want: fmt.Sprintf(`[{"schemaname":"public","viewname":"%s","viewowner":"postgres"}]`, viewName1),
|
||||
want: fmt.Sprintf(`[{"schema_name":"public","view_name":"%s","owner_name":"postgres","definition":" SELECT 1 AS col;"}]`, viewName),
|
||||
},
|
||||
{
|
||||
name: "invoke list_views with non-existent_view",
|
||||
requestBody: bytes.NewBuffer([]byte(`{"viewname": "non_existent_view"}`)),
|
||||
requestBody: bytes.NewBuffer([]byte(`{"view_name": "non_existent_view"}`)),
|
||||
wantStatusCode: http.StatusOK,
|
||||
want: `null`,
|
||||
},
|
||||
@@ -1350,6 +1351,7 @@ func RunPostgresListSchemasTest(t *testing.T, ctx context.Context, pool *pgxpool
|
||||
requestBody io.Reader
|
||||
wantStatusCode int
|
||||
want []map[string]any
|
||||
compareSubset bool
|
||||
}{
|
||||
{
|
||||
name: "invoke list_schemas with schema_name",
|
||||
@@ -1357,6 +1359,19 @@ func RunPostgresListSchemasTest(t *testing.T, ctx context.Context, pool *pgxpool
|
||||
wantStatusCode: http.StatusOK,
|
||||
want: []map[string]any{wantSchema},
|
||||
},
|
||||
{
|
||||
name: "invoke list_schemas with owner name",
|
||||
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"owner": "%s"}`, "postgres"))),
|
||||
wantStatusCode: http.StatusOK,
|
||||
want: []map[string]any{wantSchema},
|
||||
compareSubset: true,
|
||||
},
|
||||
{
|
||||
name: "invoke list_schemas with limit 1",
|
||||
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"schema_name": "%s","limit": 1}`, schemaName))),
|
||||
wantStatusCode: http.StatusOK,
|
||||
want: []map[string]any{wantSchema},
|
||||
},
|
||||
{
|
||||
name: "invoke list_schemas with non-existent schema",
|
||||
requestBody: bytes.NewBuffer([]byte(`{"schema_name": "non_existent_schema"}`)),
|
||||
@@ -1392,8 +1407,25 @@ func RunPostgresListSchemasTest(t *testing.T, ctx context.Context, pool *pgxpool
|
||||
t.Fatalf("failed to unmarshal nested result string: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Unexpected result (-want +got):\n%s", diff)
|
||||
if tc.compareSubset {
|
||||
// Assert that the 'wantTrigger' is present in the 'got' list.
|
||||
found := false
|
||||
for _, resultSchema := range got {
|
||||
if resultSchema["schema_name"] == wantSchema["schema_name"] {
|
||||
found = true
|
||||
if diff := cmp.Diff(wantSchema, resultSchema); diff != "" {
|
||||
t.Errorf("Mismatch in fields for the expected trigger (-want +got):\n%s", diff)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected schema '%s' not found in the list of all schemas.", wantSchema)
|
||||
}
|
||||
} else {
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2179,15 +2211,15 @@ func RunPostgresListSequencesTest(t *testing.T, ctx context.Context, pool *pgxpo
|
||||
defer teardown(t)
|
||||
|
||||
wantSequence := map[string]any{
|
||||
"sequencename": sequenceName,
|
||||
"schemaname": "public",
|
||||
"sequenceowner": "postgres",
|
||||
"data_type": "bigint",
|
||||
"start_value": float64(1),
|
||||
"min_value": float64(1),
|
||||
"max_value": float64(9223372036854775807),
|
||||
"increment_by": float64(1),
|
||||
"last_value": nil,
|
||||
"sequence_name": sequenceName,
|
||||
"schema_name": "public",
|
||||
"sequence_owner": "postgres",
|
||||
"data_type": "bigint",
|
||||
"start_value": float64(1),
|
||||
"min_value": float64(1),
|
||||
"max_value": float64(9223372036854775807),
|
||||
"increment_by": float64(1),
|
||||
"last_value": nil,
|
||||
}
|
||||
|
||||
invokeTcs := []struct {
|
||||
@@ -2199,13 +2231,13 @@ func RunPostgresListSequencesTest(t *testing.T, ctx context.Context, pool *pgxpo
|
||||
}{
|
||||
{
|
||||
name: "invoke list_sequences",
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf(`{"sequencename": "%s"}`, sequenceName)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf(`{"sequence_name": "%s"}`, sequenceName)),
|
||||
wantStatusCode: http.StatusOK,
|
||||
want: []map[string]any{wantSequence},
|
||||
},
|
||||
{
|
||||
name: "invoke list_sequences with non-existent sequence",
|
||||
requestBody: bytes.NewBufferString(`{"sequencename": "non_existent_sequence"}`),
|
||||
requestBody: bytes.NewBufferString(`{"sequence_name": "non_existent_sequence"}`),
|
||||
wantStatusCode: http.StatusOK,
|
||||
want: nil,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user