mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-11 08:28:11 -05:00
Compare commits
14 Commits
fix-ci
...
upload-yam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b09b69d2e | ||
|
|
3ebc3d0907 | ||
|
|
d91bdfcbdc | ||
|
|
c60a9601b4 | ||
|
|
bff528093d | ||
|
|
34f78bd89d | ||
|
|
000d6ada38 | ||
|
|
a09f628b52 | ||
|
|
80a8ebfa0b | ||
|
|
0588e178d6 | ||
|
|
f80f18aaf6 | ||
|
|
f79cdd6144 | ||
|
|
c3a58e1d16 | ||
|
|
c7b443d94a |
@@ -62,7 +62,6 @@ steps:
|
||||
postgressql \
|
||||
postgresexecutesql
|
||||
|
||||
|
||||
- id: "alloydb-pg"
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
@@ -121,8 +120,7 @@ steps:
|
||||
- "BIGTABLE_PROJECT=$PROJECT_ID"
|
||||
- "BIGTABLE_INSTANCE=$_BIGTABLE_INSTANCE"
|
||||
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||
secretEnv:
|
||||
["CLIENT_ID"]
|
||||
secretEnv: ["CLIENT_ID"]
|
||||
volumes:
|
||||
- name: "go"
|
||||
path: "/gopath"
|
||||
@@ -153,7 +151,7 @@ steps:
|
||||
"BigQuery" \
|
||||
bigquery \
|
||||
bigquery
|
||||
|
||||
|
||||
- id: "dataplex"
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
@@ -274,8 +272,7 @@ steps:
|
||||
- "CLOUD_SQL_MYSQL_DATABASE=$_DATABASE_NAME"
|
||||
- "CLOUD_SQL_MYSQL_REGION=$_REGION"
|
||||
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||
secretEnv:
|
||||
["CLOUD_SQL_MYSQL_USER", "CLOUD_SQL_MYSQL_PASS", "CLIENT_ID"]
|
||||
secretEnv: ["CLOUD_SQL_MYSQL_USER", "CLOUD_SQL_MYSQL_PASS", "CLIENT_ID"]
|
||||
volumes:
|
||||
- name: "go"
|
||||
path: "/gopath"
|
||||
@@ -387,7 +384,7 @@ steps:
|
||||
sqlite
|
||||
|
||||
- id: "couchbase"
|
||||
name : golang:1
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
entrypoint: /bin/bash
|
||||
env:
|
||||
@@ -395,7 +392,8 @@ steps:
|
||||
- "COUCHBASE_SCOPE=$_COUCHBASE_SCOPE"
|
||||
- "COUCHBASE_BUCKET=$_COUCHBASE_BUCKET"
|
||||
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||
secretEnv: ["COUCHBASE_CONNECTION", "COUCHBASE_USER", "COUCHBASE_PASS", "CLIENT_ID"]
|
||||
secretEnv:
|
||||
["COUCHBASE_CONNECTION", "COUCHBASE_USER", "COUCHBASE_PASS", "CLIENT_ID"]
|
||||
volumes:
|
||||
- name: "go"
|
||||
path: "/gopath"
|
||||
@@ -408,7 +406,7 @@ steps:
|
||||
couchbase
|
||||
|
||||
- id: "redis"
|
||||
name : golang:1
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
entrypoint: /bin/bash
|
||||
env:
|
||||
@@ -425,9 +423,9 @@ steps:
|
||||
"Redis" \
|
||||
redis \
|
||||
redis
|
||||
|
||||
|
||||
- id: "valkey"
|
||||
name : golang:1
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
entrypoint: /bin/bash
|
||||
env:
|
||||
@@ -475,7 +473,13 @@ steps:
|
||||
- "FIRESTORE_PROJECT=$PROJECT_ID"
|
||||
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||
- "LOOKER_VERIFY_SSL=$_LOOKER_VERIFY_SSL"
|
||||
secretEnv: ["CLIENT_ID", "LOOKER_BASE_URL", "LOOKER_CLIENT_ID", "LOOKER_CLIENT_SECRET"]
|
||||
secretEnv:
|
||||
[
|
||||
"CLIENT_ID",
|
||||
"LOOKER_BASE_URL",
|
||||
"LOOKER_CLIENT_ID",
|
||||
"LOOKER_CLIENT_SECRET",
|
||||
]
|
||||
volumes:
|
||||
- name: "go"
|
||||
path: "/gopath"
|
||||
@@ -487,25 +491,6 @@ steps:
|
||||
looker \
|
||||
looker
|
||||
|
||||
- id: "duckdb"
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
entrypoint: /bin/bash
|
||||
env:
|
||||
- "GOPATH=/gopath"
|
||||
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||
volumes:
|
||||
- name: "go"
|
||||
path: "/gopath"
|
||||
secretEnv: ["CLIENT_ID"]
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
.ci/test_with_coverage.sh \
|
||||
"DuckDB" \
|
||||
duckdb \
|
||||
duckdb
|
||||
|
||||
- id: "alloydbwaitforoperation"
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
@@ -545,8 +530,8 @@ steps:
|
||||
.ci/test_with_coverage.sh \
|
||||
"TiDB" \
|
||||
tidb \
|
||||
tidbsql tidbexecutesql
|
||||
|
||||
tidbsql tidbexecutesql
|
||||
|
||||
availableSecrets:
|
||||
secretManager:
|
||||
- versionName: projects/$PROJECT_ID/secrets/cloud_sql_pg_user/versions/latest
|
||||
|
||||
1
.github/release-please.yml
vendored
1
.github/release-please.yml
vendored
@@ -24,6 +24,7 @@ extraFiles: [
|
||||
"docs/en/getting-started/local_quickstart_js.md",
|
||||
"docs/en/getting-started/local_quickstart_go.md",
|
||||
"docs/en/getting-started/mcp_quickstart/_index.md",
|
||||
"docs/en/samples/alloydb/_index.md",
|
||||
"docs/en/samples/bigquery/local_quickstart.md",
|
||||
"docs/en/samples/bigquery/mcp_quickstart/_index.md",
|
||||
"docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb",
|
||||
|
||||
2
.github/workflows/schedule_reporter.yml
vendored
2
.github/workflows/schedule_reporter.yml
vendored
@@ -26,4 +26,4 @@ jobs:
|
||||
contents: 'read'
|
||||
uses: ./.github/workflows/cloud_build_failure_reporter.yml
|
||||
with:
|
||||
trigger_names: "toolbox-test-nightly,toolbox-test-on-merge"
|
||||
trigger_names: "toolbox-test-nightly,toolbox-test-on-merge,toolbox-continuous-release"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.10.0...v0.11.0) (2025-08-04)
|
||||
## [0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.11.0...v0.11.0) (2025-08-05)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
@@ -10,12 +10,16 @@
|
||||
|
||||
### Features
|
||||
|
||||
* Add DuckDB source and tool ([#879](https://github.com/googleapis/genai-toolbox/pull/879)) ([fd14933](https://github.com/googleapis/genai-toolbox/commit/fd149337e9fa8e912e8699962a7104d51cdffc5d))
|
||||
* Add TiDB source and tool ([#829](https://github.com/googleapis/genai-toolbox/issues/829)) ([6eaf36a](https://github.com/googleapis/genai-toolbox/commit/6eaf36ac8505d523fa4f5a4ac3c97209fd688cef))
|
||||
* Interactive web UI for Toolbox ([#1065](https://github.com/googleapis/genai-toolbox/issues/1065)) ([8749b03](https://github.com/googleapis/genai-toolbox/commit/8749b030035e65361047c4ead13dfacb8e9a9b59))
|
||||
* **prebuiltconfigs/cloud-sql-postgres:** Introduce additional parameter to limit context in list tables ([#1062](https://github.com/googleapis/genai-toolbox/issues/1062)) ([c3a58e1](https://github.com/googleapis/genai-toolbox/commit/c3a58e1d1678dc14d8de5006511df597fd75faa3))
|
||||
* **tools/looker-query-url:** Add support for `looker-query-url` tool ([#1015](https://github.com/googleapis/genai-toolbox/issues/1015)) ([327ddf0](https://github.com/googleapis/genai-toolbox/commit/327ddf0439058aa5ecd2c7ae8251fcde6aeff18c))
|
||||
* **tools/dataplex-lookup-entry:** Add support for `dataplex-lookup-entry` tool ([#1009](https://github.com/googleapis/genai-toolbox/issues/1009)) ([5fa1660](https://github.com/googleapis/genai-toolbox/commit/5fa1660fc8631989b4d13abea205b6426bb506a5))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tools/bigquery,mssql,mysql,postgres,spanner,tidb:** Add query logging to execute-sql tools ([#1069](https://github.com/googleapis/genai-toolbox/issues/1069)) ([0527532]([https://github.com/googleapis/genai-toolbox/commit/0527532bd7085ef9eb8f9c30f430a2f2f35cef32))
|
||||
|
||||
## [0.10.0](https://github.com/googleapis/genai-toolbox/compare/v0.9.0...v0.10.0) (2025-07-25)
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ To install Toolbox as a binary:
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```sh
|
||||
# see releases page for other versions
|
||||
export VERSION=0.10.0
|
||||
export VERSION=0.11.0
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
|
||||
chmod +x toolbox
|
||||
```
|
||||
@@ -127,7 +127,7 @@ You can also install Toolbox as a container:
|
||||
|
||||
```sh
|
||||
# see releases page for other versions
|
||||
export VERSION=0.10.0
|
||||
export VERSION=0.11.0
|
||||
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
|
||||
```
|
||||
|
||||
@@ -151,7 +151,7 @@ To install from source, ensure you have the latest version of
|
||||
[Go installed](https://go.dev/doc/install), and then run the following command:
|
||||
|
||||
```sh
|
||||
go install github.com/googleapis/genai-toolbox@v0.10.0
|
||||
go install github.com/googleapis/genai-toolbox@v0.11.0
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexlookupentry"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexsearchentries"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/dgraph"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/duckdbsql"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoredeletedocuments"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoregetdocuments"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoregetrules"
|
||||
@@ -112,7 +111,6 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/couchbase"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/dataplex"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/dgraph"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/duckdb"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/firestore"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/http"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/looker"
|
||||
|
||||
@@ -86,7 +86,7 @@ To install Toolbox as a binary:
|
||||
|
||||
```sh
|
||||
# see releases page for other versions
|
||||
export VERSION=0.10.0
|
||||
export VERSION=0.11.0
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
|
||||
chmod +x toolbox
|
||||
```
|
||||
@@ -97,7 +97,7 @@ You can also install Toolbox as a container:
|
||||
|
||||
```sh
|
||||
# see releases page for other versions
|
||||
export VERSION=0.10.0
|
||||
export VERSION=0.11.0
|
||||
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
|
||||
```
|
||||
|
||||
@@ -115,7 +115,7 @@ To install from source, ensure you have the latest version of
|
||||
[Go installed](https://go.dev/doc/install), and then run the following command:
|
||||
|
||||
```sh
|
||||
go install github.com/googleapis/genai-toolbox@v0.10.0
|
||||
go install github.com/googleapis/genai-toolbox@v0.11.0
|
||||
```
|
||||
|
||||
{{% /tab %}}
|
||||
|
||||
@@ -171,7 +171,7 @@ In this section, we will download Toolbox, configure our tools in a
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ In this section, we will download Toolbox, configure our tools in a
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ In this section, we will download Toolbox, configure our tools in a
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ In this section, we will download Toolbox, configure our tools in a
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -62,19 +62,19 @@ to expose your developer assistant tools to a Firestore instance:
|
||||
<!-- {x-release-please-start-version} -->
|
||||
{{< tabpane persist=header >}}
|
||||
{{< tab header="linux/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="windows/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox
|
||||
{{< /tab >}}
|
||||
{{< /tabpane >}}
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
@@ -46,19 +46,19 @@ to expose your developer assistant tools to a Looker instance:
|
||||
<!-- {x-release-please-start-version} -->
|
||||
{{< tabpane persist=header >}}
|
||||
{{< tab header="linux/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="windows/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox.exe
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
|
||||
{{< /tab >}}
|
||||
{{< /tabpane >}}
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
@@ -52,19 +52,19 @@ Omni](https://cloud.google.com/alloydb/omni/current/docs/overview).
|
||||
<!-- {x-release-please-start-version} -->
|
||||
{{< tabpane persist=header >}}
|
||||
{{< tab header="linux/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab header="windows/amd64" lang="bash" >}}
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox.exe
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
|
||||
{{< /tab >}}
|
||||
{{< /tabpane >}}
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
@@ -33,6 +33,14 @@ cluster][alloydb-free-trial].
|
||||
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
|
||||
Run parameterized SQL statements in AlloyDB Postgres.
|
||||
|
||||
### Pre-built Configurations
|
||||
|
||||
- [AlloyDB using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/alloydb_pg_mcp/)
|
||||
Connect your IDE to AlloyDB using Toolbox.
|
||||
|
||||
- [AlloyDB Admin API using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/alloydb_pg_admin_mcp/)
|
||||
Create your AlloyDB database with MCP Toolbox.
|
||||
|
||||
## Requirements
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
@@ -54,6 +54,11 @@ avoiding full table scans or complex filters.
|
||||
- [`bigquery-list-table-ids`](../tools/bigquery/bigquery-list-table-ids.md)
|
||||
List tables in a given dataset.
|
||||
|
||||
### Pre-built Configurations
|
||||
|
||||
- [BigQuery using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/bigquery_mcp/)
|
||||
Connect your IDE to BigQuery using Toolbox.
|
||||
|
||||
## Requirements
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
@@ -27,6 +27,11 @@ to a database by following these instructions][csql-mssql-connect].
|
||||
- [`mssql-execute-sql`](../tools/mssql/mssql-execute-sql.md)
|
||||
Run parameterized SQL Server queries in Cloud SQL for SQL Server.
|
||||
|
||||
### Pre-built Configurations
|
||||
|
||||
- [Cloud SQL for SQL Server using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mssql_mcp/)
|
||||
Connect your IDE to Cloud SQL for SQL Server using Toolbox.
|
||||
|
||||
## Requirements
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
@@ -28,6 +28,11 @@ to a database by following these instructions][csql-mysql-quickstart].
|
||||
- [`mysql-execute-sql`](../tools/mysql/mysql-execute-sql.md)
|
||||
Run parameterized SQL queries in Cloud SQL for MySQL.
|
||||
|
||||
### Pre-built Configurations
|
||||
|
||||
- [Cloud SQL for MySQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mysql_mcp/)
|
||||
Connect your IDE to Cloud SQL for MySQL using Toolbox.
|
||||
|
||||
## Requirements
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
@@ -28,6 +28,12 @@ to a database by following these instructions][csql-pg-quickstart].
|
||||
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
|
||||
Run parameterized SQL statements in PostgreSQL.
|
||||
|
||||
### Pre-built Configurations
|
||||
|
||||
- [Cloud SQL for Postgres using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_pg_mcp/)
|
||||
Connect your IDE to Cloud SQL for Postgres using Toolbox.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
title: DuckDB
|
||||
linkTitle: DuckDB
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
DuckDB is an in-process SQL OLAP database management system designed for analytical query processing.
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
[DuckDB](https://duckdb.org/) is an embedded analytical database management system that runs in-process with the client application. It is optimized for analytical workloads, providing high performance for complex queries with minimal setup.
|
||||
|
||||
DuckDB has the following notable characteristics:
|
||||
|
||||
- In-process, serverless database engine
|
||||
- Supports complex SQL queries for analytical processing
|
||||
- Can operate on in-memory or persistent storage
|
||||
- Zero-configuration - no external dependencies or server setup required
|
||||
- Highly optimized for columnar data storage and query execution
|
||||
|
||||
For more details, refer to the [DuckDB Documentation](https://duckdb.org/).
|
||||
|
||||
## Available Tools
|
||||
- [`duckdb-sql`](../tools/duckdb/duckdb-sql.md)
|
||||
Execute pre-defined prepared SQL queries in DuckDB.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Database File
|
||||
|
||||
To use DuckDB, you can either:
|
||||
|
||||
- Specify a file path for a persistent database stored on the filesystem
|
||||
- Omit the file path to use an in-memory database
|
||||
|
||||
## Example
|
||||
|
||||
For a persistent DuckDB database:
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
my-duckdb:
|
||||
kind: "duckdb"
|
||||
dbFilePath: "/path/to/database.db"
|
||||
configuration:
|
||||
memory_limit: "2GB"
|
||||
threads: "4"
|
||||
```
|
||||
|
||||
For an in-memory DuckDB database:
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
my-duckdb-memory:
|
||||
name: "my-duckdb-memory"
|
||||
kind: "duckdb"
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Configuration Fields
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------------|:-----------------:|:------------:|---------------------------------------------------------------------------------|
|
||||
| kind | string | true | Must be "duckdb". |
|
||||
| dbFilePath | string | false | Path to the DuckDB database file. Omit for an in-memory database. |
|
||||
| configuration | map[string]string | false | Additional DuckDB configuration options (e.g., `memory_limit`, `threads`). |
|
||||
|
||||
For a complete list of available configuration options, refer to the [DuckDB Configuration Documentation](https://duckdb.org/docs/stable/configuration/overview.html#local-configuration-options).
|
||||
|
||||
|
||||
For more details on the Go implementation, see the [go-duckdb package documentation](https://pkg.go.dev/github.com/scottlepp/go-duckdb#section-readme).
|
||||
@@ -23,6 +23,11 @@ reputation for reliability, feature robustness, and performance.
|
||||
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
|
||||
Run parameterized SQL statements in PostgreSQL.
|
||||
|
||||
### Pre-built Configurations
|
||||
|
||||
- [PostgreSQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/postgres_mcp/)
|
||||
Connect your IDE to PostgreSQL using Toolbox.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Database User
|
||||
|
||||
@@ -31,6 +31,11 @@ the Google Cloud console][spanner-quickstart].
|
||||
- [`spanner-execute-sql`](../tools/spanner/spanner-execute-sql.md)
|
||||
Run structured and parameterized queries on Spanner.
|
||||
|
||||
### Pre-built Configurations
|
||||
|
||||
- [Spanner using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/spanner_mcp/)
|
||||
Connect your IDE to Spanner using Toolbox.
|
||||
|
||||
## Requirements
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
@@ -75,6 +75,12 @@ visible to the LLM.
|
||||
|
||||
[alloydb-psv]: https://cloud.google.com/alloydb/docs/parameterized-secure-views-overview
|
||||
|
||||
{{< notice tip >}} Make sure to enable the `parameterized_views` extension before running this tool. You can do so by running this command in the AlloyDB studio:
|
||||
```sql
|
||||
CREATE EXTENSION IF NOT EXISTS parameterized_views;
|
||||
```
|
||||
{{< /notice >}}
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
@@ -95,7 +101,6 @@ tools:
|
||||
- name: my_google_service
|
||||
field: email
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: "DuckDB"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
Tools that work with DuckDB Sources.
|
||||
---
|
||||
@@ -1,80 +0,0 @@
|
||||
---
|
||||
title: "duckdb-sql"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
Execute SQL statements against a DuckDB database using the DuckDB SQL tools configuration.
|
||||
aliases:
|
||||
- /resources/tools/duckdb-sql
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `duckdb-sql` tool executes a pre-defined SQL statement against a [DuckDB](https://duckdb.org/) database. It is compatible with any DuckDB source configuration as defined in the [DuckDB source documentation](../../sources/duckdb.md).
|
||||
|
||||
The specified SQL statement is executed as a prepared statement, and parameters are inserted according to their position: e.g., `$1` is the first parameter, `$2` is the second, and so on. If template parameters are included, they are resolved before execution of the prepared statement.
|
||||
|
||||
DuckDB's SQL dialect closely follows the conventions of the PostgreSQL dialect, with a few exceptions listed in the [DuckDB PostgreSQL Compatibility documentation](https://duckdb.org/docs/stable/sql/dialect/postgresql_compatibility.html). For an introduction to DuckDB's SQL dialect, refer to the [DuckDB SQL Introduction](https://duckdb.org/docs/stable/sql/introduction).
|
||||
|
||||
### Concepts
|
||||
|
||||
DuckDB is a relational database management system (RDBMS). Data is stored in relations (tables), where each table is a named collection of rows. Each row in a table has the same set of named columns, each with a specific data type. Tables are stored within schemas, and a collection of schemas constitutes the entire database.
|
||||
|
||||
For more details, see the [DuckDB SQL Introduction](https://duckdb.org/docs/stable/sql/introduction).
|
||||
|
||||
## Example
|
||||
|
||||
> **Note:** This tool uses parameterized queries to prevent SQL injections. Query parameters can be used as substitutes for arbitrary expressions but cannot be used for identifiers, column names, table names, or other parts of the query.
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
search-users:
|
||||
kind: duckdb-sql
|
||||
source: my-duckdb
|
||||
description: Search users by name and age
|
||||
statement: SELECT * FROM users WHERE name LIKE $1 AND age >= $2
|
||||
parameters:
|
||||
- name: name
|
||||
type: string
|
||||
description: The name to search for
|
||||
- name: min_age
|
||||
type: integer
|
||||
description: Minimum age
|
||||
```
|
||||
|
||||
## Example with Template Parameters
|
||||
|
||||
> **Note:** Template parameters allow direct modifications to the SQL statement, including identifiers, column names, and table names, which makes them more vulnerable to SQL injections. Using basic parameters (see above) is recommended for performance and safety. For more details, see the [templateParameters](../#template-parameters) section.
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
list_table:
|
||||
kind: duckdb-sql
|
||||
source: my-duckdb
|
||||
statement: |
|
||||
SELECT * FROM {{.tableName}};
|
||||
description: |
|
||||
Use this tool to list all information from a specific table.
|
||||
Example:
|
||||
{{
|
||||
"tableName": "flights",
|
||||
}}
|
||||
templateParameters:
|
||||
- name: tableName
|
||||
type: string
|
||||
description: Table to select from
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Configuration Fields
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|--------------------|:-------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| kind | string | true | Must be "duckdb-sql". |
|
||||
| source | string | true | Name of the DuckDB source configuration (see [DuckDB source documentation](../../sources/duckdb.md)). |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
| statement | string | true | The SQL statement to execute. |
|
||||
| authRequired | []string | false | List of authentication requirements for the tool (if any). |
|
||||
| parameters | [parameters](../#specifying-parameters) | false | List of parameters that will be inserted into the SQL statement |
|
||||
| templateParameters | [templateParameters](../#template-parameters) | false | List of template parameters that will be inserted into the SQL statement before executing the prepared statement. |
|
||||
@@ -114,7 +114,7 @@ In this section, we will download and install the Toolbox binary.
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
export VERSION="0.10.0"
|
||||
export VERSION="0.11.0"
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
@@ -179,7 +179,7 @@ to use BigQuery, and then run the Toolbox server.
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ In this section, we will download Toolbox, configure our tools in a
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
|
||||
<!-- {x-release-please-start-version} -->
|
||||
```bash
|
||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
|
||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
|
||||
```
|
||||
<!-- {x-release-please-end} -->
|
||||
|
||||
|
||||
22
go.mod
22
go.mod
@@ -11,7 +11,7 @@ require (
|
||||
cloud.google.com/go/cloudsqlconn v1.17.3
|
||||
cloud.google.com/go/dataplex v1.26.0
|
||||
cloud.google.com/go/firestore v1.18.0
|
||||
cloud.google.com/go/spanner v1.83.0
|
||||
cloud.google.com/go/spanner v1.84.0
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0
|
||||
github.com/couchbase/gocb/v2 v2.10.1
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/looker-open-source/sdk-codegen/go v0.25.10
|
||||
github.com/microsoft/go-mssqldb v1.9.2
|
||||
github.com/neo4j/neo4j-go-driver/v5 v5.28.1
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
github.com/redis/go-redis/v9 v9.12.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/thlib/go-timezone-local v0.0.7
|
||||
github.com/valkey-io/valkey-go v1.0.63
|
||||
@@ -45,26 +45,19 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0
|
||||
go.opentelemetry.io/otel/trace v1.37.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
google.golang.org/api v0.244.0
|
||||
google.golang.org/api v0.245.0
|
||||
modernc.org/sqlite v1.38.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings v0.1.17 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 // indirect
|
||||
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 // indirect
|
||||
github.com/marcboeker/go-duckdb/mapping v0.0.11 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
gonum.org/v1/gonum v0.16.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go v0.121.2 // indirect
|
||||
cloud.google.com/go v0.121.4 // indirect
|
||||
cloud.google.com/go/alloydb v1.18.0 // indirect
|
||||
cloud.google.com/go/auth v0.16.3 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
@@ -79,7 +72,6 @@ require (
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/apache/arrow-go/v18 v18.4.0 // indirect
|
||||
github.com/apache/arrow/go/v15 v15.0.2 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -100,7 +92,6 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@@ -120,7 +111,6 @@ require (
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/marcboeker/go-duckdb/v2 v2.3.4
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -160,7 +150,7 @@ require (
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
|
||||
53
go.sum
53
go.sum
@@ -38,8 +38,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
|
||||
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
|
||||
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
|
||||
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
|
||||
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
|
||||
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
|
||||
cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs=
|
||||
cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s=
|
||||
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
|
||||
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
|
||||
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
|
||||
@@ -544,8 +544,8 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+
|
||||
cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
|
||||
cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
|
||||
cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
|
||||
cloud.google.com/go/spanner v1.83.0 h1:AH3QIoSIa01l3WbeTppkwCEYFNK1AER6drcYhPmwhxY=
|
||||
cloud.google.com/go/spanner v1.83.0/go.mod h1:QSWcjxszT0WRHNd8zyGI0WctrYA1N7j0yTFsWyol9Yw=
|
||||
cloud.google.com/go/spanner v1.84.0 h1:McPTc6g7hmZfvBlY2mwxl4nJ3+oVtDOl3yGvxIyDTKk=
|
||||
cloud.google.com/go/spanner v1.84.0/go.mod h1:Klo9oQcfxr32vh+2iuA34c6rHjjSpMcU23Jy6DArz7I=
|
||||
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
|
||||
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
|
||||
cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
|
||||
@@ -563,8 +563,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL
|
||||
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
|
||||
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
|
||||
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
|
||||
cloud.google.com/go/storage v1.53.0 h1:gg0ERZwL17pJ+Cz3cD2qS60w1WMDnwcm5YPAIQBHUAw=
|
||||
cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5it1i5boaA=
|
||||
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
|
||||
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
|
||||
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
|
||||
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
|
||||
cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
|
||||
@@ -674,20 +674,14 @@ github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/arrow-go/v18 v18.4.0 h1:/RvkGqH517iY8bZKc4FD5/kkdwXJGjxf28JIXbJ/oB0=
|
||||
github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOpkYpdmGRHcw14=
|
||||
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
|
||||
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
|
||||
github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
|
||||
github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA=
|
||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
|
||||
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
@@ -749,18 +743,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/duckdb/duckdb-go-bindings v0.1.17 h1:SjpRwrJ7v0vqnIvLeVFHlhuS72+Lp8xxQ5jIER2LZP4=
|
||||
github.com/duckdb/duckdb-go-bindings v0.1.17/go.mod h1:pBnfviMzANT/9hi4bg+zW4ykRZZPCXlVuvBWEcZofkc=
|
||||
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 h1:8CLBnsq9YDhi2Gmt3sjSUeXxMzyMQAKefjqUy9zVPFk=
|
||||
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12/go.mod h1:Ezo7IbAfB8NP7CqPIN8XEHKUg5xdRRQhcPPlCXImXYA=
|
||||
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 h1:wjO4I0GhMh2xIpiUgRpzuyOT4KxXLoUS/rjU7UUVvCE=
|
||||
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12/go.mod h1:eS7m/mLnPQgVF4za1+xTyorKRBuK0/BA44Oy6DgrGXI=
|
||||
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 h1:HzKQi2C+1jzmwANsPuYH6x9Sfw62SQTjNAEq3OySKFI=
|
||||
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12/go.mod h1:1GOuk1PixiESxLaCGFhag+oFi7aP+9W8byymRAvunBk=
|
||||
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 h1:YGSR7AFLw2gJ7IbgLE6DkKYmgKv1LaRSd/ZKF1yh2oE=
|
||||
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12/go.mod h1:o7crKMpT2eOIi5/FY6HPqaXcvieeLSqdXXaXbruGX7w=
|
||||
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 h1:2aduW6fnFnT2Q45PlIgHbatsPOxV9WSZ5B2HzFfxaxA=
|
||||
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12/go.mod h1:IlOhJdVKUJCAPj3QsDszUo8DVdvp1nBFp4TUJVdw99s=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
@@ -836,8 +818,6 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
@@ -1021,7 +1001,6 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
|
||||
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
@@ -1046,12 +1025,6 @@ github.com/looker-open-source/sdk-codegen/go v0.25.10/go.mod h1:YM/IYSsTPk7I54j4
|
||||
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
||||
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 h1:G1W+GVnUefR8uy7jHdNO+CRMsmFG5mFPIHVAespfFCA=
|
||||
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10/go.mod h1:jccUb8TYD0p5TsEEeN4SXuslNJHo23QaKOqKD+U6uFU=
|
||||
github.com/marcboeker/go-duckdb/mapping v0.0.11 h1:fusN1b1l7Myxafifp596I6dNLNhN5Uv/rw31qAqBwqw=
|
||||
github.com/marcboeker/go-duckdb/mapping v0.0.11/go.mod h1:aYBjFLgfKO0aJIbDtXPiaL5/avRQISveX/j9tMf9JhU=
|
||||
github.com/marcboeker/go-duckdb/v2 v2.3.4 h1:o98wrefPbH0IdJRix4pF0+jZiXoFQ+FSR8InMsCUZD0=
|
||||
github.com/marcboeker/go-duckdb/v2 v2.3.4/go.mod h1:8adNrftF4Ye29XMrpIl5NYNosTVsZu1mz3C82WdVvrk=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -1059,9 +1032,7 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs=
|
||||
github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
|
||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
|
||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
@@ -1098,8 +1069,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.12.0 h1:XlVPGlflh4nxfhsNXPA8Qp6EmEfTo0rp8oaBzPipXnU=
|
||||
github.com/redis/go-redis/v9 v9.12.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
@@ -1696,8 +1667,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
|
||||
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
|
||||
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
|
||||
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
|
||||
google.golang.org/api v0.244.0 h1:lpkP8wVibSKr++NCD36XzTk/IzeKJ3klj7vbj+XU5pE=
|
||||
google.golang.org/api v0.244.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
|
||||
google.golang.org/api v0.245.0 h1:YliGvz1rjXB+sTLNIST6Ffeji9WlRdLQ+LPl9ruSa5Y=
|
||||
google.golang.org/api v0.245.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1840,8 +1811,8 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
||||
@@ -72,26 +72,36 @@ tools:
|
||||
SELECT
|
||||
ti.schema_name,
|
||||
ti.table_name AS object_name,
|
||||
json_build_object(
|
||||
'schema_name', ti.schema_name,
|
||||
'object_name', ti.table_name,
|
||||
'object_type', CASE ti.object_kind
|
||||
WHEN 'r' THEN 'TABLE'
|
||||
WHEN 'p' THEN 'PARTITIONED TABLE'
|
||||
ELSE ti.object_kind::text -- Should not happen due to WHERE clause
|
||||
END,
|
||||
'owner', ti.table_owner,
|
||||
'comment', ti.table_comment,
|
||||
'columns', COALESCE((SELECT json_agg(json_build_object('column_name',ci.column_name,'data_type',ci.data_type,'ordinal_position',ci.column_ordinal_position,'is_not_nullable',ci.is_not_nullable,'column_default',ci.column_default,'column_comment',ci.column_comment) ORDER BY ci.column_ordinal_position) FROM columns_info ci WHERE ci.table_oid = ti.table_oid), '[]'::json),
|
||||
'constraints', COALESCE((SELECT json_agg(json_build_object('constraint_name',cons.constraint_name,'constraint_type',cons.constraint_type,'constraint_definition',cons.constraint_definition,'constraint_columns',cons.constraint_columns,'foreign_key_referenced_table',cons.foreign_key_referenced_table,'foreign_key_referenced_columns',cons.foreign_key_referenced_columns)) FROM constraints_info cons WHERE cons.table_oid = ti.table_oid), '[]'::json),
|
||||
'indexes', COALESCE((SELECT json_agg(json_build_object('index_name',ii.index_name,'index_definition',ii.index_definition,'is_unique',ii.is_unique,'is_primary',ii.is_primary,'index_method',ii.index_method,'index_columns',ii.index_columns)) FROM indexes_info ii WHERE ii.table_oid = ti.table_oid), '[]'::json),
|
||||
'triggers', COALESCE((SELECT json_agg(json_build_object('trigger_name',tri.trigger_name,'trigger_definition',tri.trigger_definition,'trigger_enabled_state',tri.trigger_enabled_state)) FROM triggers_info tri WHERE tri.table_oid = ti.table_oid), '[]'::json)
|
||||
) AS object_details
|
||||
CASE
|
||||
WHEN $2 = 'simple' THEN
|
||||
-- IF format is 'simple', return basic JSON
|
||||
json_build_object('name', ti.table_name)
|
||||
ELSE
|
||||
json_build_object(
|
||||
'schema_name', ti.schema_name,
|
||||
'object_name', ti.table_name,
|
||||
'object_type', CASE ti.object_kind
|
||||
WHEN 'r' THEN 'TABLE'
|
||||
WHEN 'p' THEN 'PARTITIONED TABLE'
|
||||
ELSE ti.object_kind::text -- Should not happen due to WHERE clause
|
||||
END,
|
||||
'owner', ti.table_owner,
|
||||
'comment', ti.table_comment,
|
||||
'columns', COALESCE((SELECT json_agg(json_build_object('column_name',ci.column_name,'data_type',ci.data_type,'ordinal_position',ci.column_ordinal_position,'is_not_nullable',ci.is_not_nullable,'column_default',ci.column_default,'column_comment',ci.column_comment) ORDER BY ci.column_ordinal_position) FROM columns_info ci WHERE ci.table_oid = ti.table_oid), '[]'::json),
|
||||
'constraints', COALESCE((SELECT json_agg(json_build_object('constraint_name',cons.constraint_name,'constraint_type',cons.constraint_type,'constraint_definition',cons.constraint_definition,'constraint_columns',cons.constraint_columns,'foreign_key_referenced_table',cons.foreign_key_referenced_table,'foreign_key_referenced_columns',cons.foreign_key_referenced_columns)) FROM constraints_info cons WHERE cons.table_oid = ti.table_oid), '[]'::json),
|
||||
'indexes', COALESCE((SELECT json_agg(json_build_object('index_name',ii.index_name,'index_definition',ii.index_definition,'is_unique',ii.is_unique,'is_primary',ii.is_primary,'index_method',ii.index_method,'index_columns',ii.index_columns)) FROM indexes_info ii WHERE ii.table_oid = ti.table_oid), '[]'::json),
|
||||
'triggers', COALESCE((SELECT json_agg(json_build_object('trigger_name',tri.trigger_name,'trigger_definition',tri.trigger_definition,'trigger_enabled_state',tri.trigger_enabled_state)) FROM triggers_info tri WHERE tri.table_oid = ti.table_oid), '[]'::json)
|
||||
)
|
||||
END AS object_details
|
||||
FROM table_info ti ORDER BY ti.schema_name, ti.table_name;
|
||||
parameters:
|
||||
- name: table_names
|
||||
type: string
|
||||
description: "Optional: A comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed."
|
||||
- name: output_format
|
||||
type: string
|
||||
description: "Optional: Use 'simple' to return table names only or use 'detailed' to return the full information schema."
|
||||
default: "detailed"
|
||||
|
||||
toolsets:
|
||||
cloud-sql-postgres-database-tools:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
--text-secondary-gray: #6e6e6e;
|
||||
--button-primary: var(--toolbox-blue);
|
||||
--button-secondary: #616161;
|
||||
--section-border: #e0e0e0;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -40,6 +41,7 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
padding-bottom: 30px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -161,6 +163,44 @@ body {
|
||||
background-color: var(--button-secondary)
|
||||
}
|
||||
|
||||
.btn--setup-gis {
|
||||
background-color: white;
|
||||
color: var(--text-primary-gray);
|
||||
border: 2px solid var(--text-primary-gray);
|
||||
}
|
||||
|
||||
.btn--clear-yaml {
|
||||
background-color: transparent;
|
||||
border: 2px solid var(--button-secondary);
|
||||
border-radius: 5px;
|
||||
color: var(--button-secondary)
|
||||
}
|
||||
|
||||
.btn--download-yaml {
|
||||
background-color: var(--button-secondary);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.btn--yaml-tab {
|
||||
background-color: transparent;
|
||||
color: var(--text-secondary-gray);
|
||||
border: 2px solid var(--text-secondary-gray);
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
font-size: 1.2em;
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
|
||||
&.active {
|
||||
background-color: #d0d0d0;
|
||||
|
||||
&:hover {
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tool-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -379,7 +419,7 @@ body {
|
||||
}
|
||||
|
||||
.auth-param-input {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--section-border);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -513,6 +553,89 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.auth-method-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.auth-method-label {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary-gray);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.auth-helper-section {
|
||||
border: 1px solid var(--section-border);
|
||||
background-color: transparent;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.auth-method-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.auth-method-details {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--section-border);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
background-color: #fff;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.auth-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.auth-input-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
& label {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary-gray);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-input {
|
||||
padding: 8px 8px;
|
||||
border: 1px solid #bdc1c6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--toolbox-blue);
|
||||
box-shadow: 0 0 0 1px #1a73e8;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-method-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.auth-instructions {
|
||||
font-size: 0.8em;
|
||||
margin-top: 5px;
|
||||
color: var(--text-secondary-gray);
|
||||
}
|
||||
|
||||
.tool-response {
|
||||
margin: 20px 0 0 0;
|
||||
|
||||
@@ -576,5 +699,178 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#yaml-editor-area {
|
||||
flex: 3;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#source-adder {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
overflow-y: auto;
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary-gray);
|
||||
}
|
||||
}
|
||||
|
||||
#yaml-uploader {
|
||||
margin-bottom: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#yaml-uploader h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary-gray);
|
||||
}
|
||||
|
||||
#yaml-uploader p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-secondary-gray);
|
||||
}
|
||||
|
||||
#controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.yaml-output-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
flex-shrink: 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
color: var(--text-primary-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary-gray);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
color: var(--text-primary-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button .material-icons {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
#yamlOutput {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
resize: none;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
#sourceTypeSelect {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.hidden-file-input {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#loadedFileName {
|
||||
margin-top: 8px;
|
||||
font-style: italic;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#fileError {
|
||||
color: red;
|
||||
margin-top: 8px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.upload-yaml-tab {
|
||||
padding-top: 15px;
|
||||
margin-top: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toggle-details-tab {
|
||||
background-color: transparent;
|
||||
color: var(--toolbox-blue);
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
140
internal/server/static/data/generate_template_json.go
Normal file
140
internal/server/static/data/generate_template_json.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
sourcesDir = "internal/sources"
|
||||
outputFile = "internal/server/static/data/source_templates.json"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
results := make(map[string][]string)
|
||||
|
||||
absSourcesDir, err := filepath.Abs(sourcesDir)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get absolute path for sources_dir: %v", err)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(absSourcesDir)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading sources directory %s: %v", absSourcesDir, err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
sourceName := entry.Name()
|
||||
goFilePath := filepath.Join(absSourcesDir, sourceName, sourceName+".go")
|
||||
|
||||
if _, err := os.Stat(goFilePath); err == nil {
|
||||
log.Printf("Processing: %s", goFilePath)
|
||||
fields, err := parseConfigFields(goFilePath)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing %s: %v", goFilePath, err)
|
||||
continue
|
||||
}
|
||||
if len(fields) > 0 {
|
||||
results[sourceName] = fields
|
||||
} else {
|
||||
log.Printf("Warning: No relevant fields found in 'Config' struct in %s", goFilePath)
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// This is fine, not all subdirs might follow the pattern
|
||||
} else {
|
||||
log.Printf("Error stating file %s: %v", goFilePath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
log.Printf("No source configs found matching the pattern in %s", absSourcesDir)
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(results, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Error marshaling JSON: %v", err)
|
||||
}
|
||||
|
||||
absOutputFile, err := filepath.Abs(outputFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get absolute path for output_file: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the output directory exists
|
||||
outputDir := filepath.Dir(absOutputFile)
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
log.Fatalf("Error creating output directory %s: %v", outputDir, err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(absOutputFile, jsonData, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Error writing output file %s: %v", absOutputFile, err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully generated %s\n", absOutputFile)
|
||||
}
|
||||
|
||||
func parseConfigFields(filename string) ([]string, error) {
|
||||
fset := token.NewFileSet()
|
||||
node, err := parser.ParseFile(fset, filename, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var fields []string
|
||||
foundConfig := false
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
if foundConfig {
|
||||
return false
|
||||
}
|
||||
typeSpec, ok := n.(*ast.TypeSpec)
|
||||
if !ok || typeSpec.Name.Name != "Config" {
|
||||
return true
|
||||
}
|
||||
|
||||
structType, ok := typeSpec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
foundConfig = true // Mark as found
|
||||
|
||||
for _, field := range structType.Fields.List {
|
||||
if len(field.Names) == 0 {
|
||||
continue
|
||||
}
|
||||
fieldName := field.Names[0].Name
|
||||
if fieldName == "Name" {
|
||||
continue
|
||||
}
|
||||
|
||||
yamlTagName := ""
|
||||
if field.Tag != nil {
|
||||
tagVal := strings.Trim(field.Tag.Value, "`")
|
||||
tags := reflect.StructTag(tagVal)
|
||||
if yamlTag, ok := tags.Lookup("yaml"); ok {
|
||||
yamlTagName = strings.Split(yamlTag, ",")[0]
|
||||
}
|
||||
}
|
||||
|
||||
if yamlTagName != "" && yamlTagName != "-" {
|
||||
fields = append(fields, yamlTagName)
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
132
internal/server/static/data/source_templates.json
Normal file
132
internal/server/static/data/source_templates.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"bigquery": [
|
||||
"kind",
|
||||
"project",
|
||||
"location"
|
||||
],
|
||||
"bigtable": [
|
||||
"kind",
|
||||
"project",
|
||||
"instance"
|
||||
],
|
||||
"couchbase": [
|
||||
"kind",
|
||||
"connectionString",
|
||||
"bucket",
|
||||
"scope",
|
||||
"username",
|
||||
"password",
|
||||
"clientCert",
|
||||
"clientCertPassword",
|
||||
"clientKey",
|
||||
"clientKeyPassword",
|
||||
"caCert",
|
||||
"noSslVerify",
|
||||
"profile",
|
||||
"queryScanConsistency"
|
||||
],
|
||||
"dataplex": [
|
||||
"kind",
|
||||
"project"
|
||||
],
|
||||
"dgraph": [
|
||||
"kind",
|
||||
"dgraphUrl",
|
||||
"user",
|
||||
"password",
|
||||
"namespace",
|
||||
"apiKey"
|
||||
],
|
||||
"duckdb": [
|
||||
"kind",
|
||||
"dbFilePath",
|
||||
"configuration"
|
||||
],
|
||||
"firestore": [
|
||||
"kind",
|
||||
"project",
|
||||
"database"
|
||||
],
|
||||
"http": [
|
||||
"kind",
|
||||
"baseUrl",
|
||||
"timeout",
|
||||
"headers",
|
||||
"queryParams",
|
||||
"disableSslVerification"
|
||||
],
|
||||
"looker": [
|
||||
"kind",
|
||||
"base_url",
|
||||
"client_id",
|
||||
"client_secret",
|
||||
"verify_ssl",
|
||||
"timeout"
|
||||
],
|
||||
"mongodb": [
|
||||
"kind",
|
||||
"uri"
|
||||
],
|
||||
"mssql": [
|
||||
"kind",
|
||||
"host",
|
||||
"port",
|
||||
"user",
|
||||
"password",
|
||||
"database",
|
||||
"encrypt"
|
||||
],
|
||||
"mysql": [
|
||||
"kind",
|
||||
"host",
|
||||
"port",
|
||||
"user",
|
||||
"password",
|
||||
"database",
|
||||
"queryTimeout"
|
||||
],
|
||||
"neo4j": [
|
||||
"kind",
|
||||
"uri",
|
||||
"user",
|
||||
"password",
|
||||
"database"
|
||||
],
|
||||
"postgres": [
|
||||
"kind",
|
||||
"host",
|
||||
"port",
|
||||
"user",
|
||||
"password",
|
||||
"database"
|
||||
],
|
||||
"redis": [
|
||||
"kind",
|
||||
"address",
|
||||
"username",
|
||||
"password",
|
||||
"database",
|
||||
"useGCPIAM",
|
||||
"clusterEnabled"
|
||||
],
|
||||
"spanner": [
|
||||
"kind",
|
||||
"project",
|
||||
"instance",
|
||||
"dialect",
|
||||
"database"
|
||||
],
|
||||
"sqlite": [
|
||||
"kind",
|
||||
"database"
|
||||
],
|
||||
"valkey": [
|
||||
"kind",
|
||||
"address",
|
||||
"username",
|
||||
"password",
|
||||
"database",
|
||||
"useGCPIAM",
|
||||
"disableCache"
|
||||
]
|
||||
}
|
||||
173
internal/server/static/js/auth.js
Normal file
173
internal/server/static/js/auth.js
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Renders the Google Sign-In button using the GIS library.
|
||||
* @param {string} toolId The ID of the tool.
|
||||
* @param {string} clientId The Google OAuth Client ID.
|
||||
* @param {string} authProfileName The name of the auth service in tools file.
|
||||
*/
|
||||
function renderGoogleSignInButton(toolId, clientId, authProfileName) {
|
||||
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
|
||||
const GIS_CONTAINER_ID = `gisContainer-${UNIQUE_ID_BASE}`;
|
||||
const gisContainer = document.getElementById(GIS_CONTAINER_ID);
|
||||
const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .btn--setup-gis`);
|
||||
|
||||
if (!gisContainer) {
|
||||
console.error('GIS container not found:', GIS_CONTAINER_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
alert('Please enter an OAuth Client ID first.');
|
||||
return;
|
||||
}
|
||||
|
||||
// hide the setup button and show the container for the GIS button
|
||||
if (setupGisBtn) setupGisBtn.style.display = 'none';
|
||||
gisContainer.innerHTML = '';
|
||||
gisContainer.style.display = 'flex';
|
||||
if (window.google && window.google.accounts && window.google.accounts.id) {
|
||||
try {
|
||||
const handleResponse = (response) => handleCredentialResponse(response, toolId, authProfileName);
|
||||
window.google.accounts.id.initialize({
|
||||
client_id: clientId,
|
||||
callback: handleResponse,
|
||||
auto_select: false
|
||||
});
|
||||
window.google.accounts.id.renderButton(
|
||||
gisContainer,
|
||||
{ theme: "outline", size: "large", text: "signin_with" }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error initializing Google Sign-In:", error);
|
||||
alert("Error initializing Google Sign-In. Check the Client ID and browser console.");
|
||||
gisContainer.innerHTML = '<p style="color: red;">Error loading Sign-In button.</p>';
|
||||
if (setupGisBtn) setupGisBtn.style.display = '';
|
||||
}
|
||||
} else {
|
||||
console.error("GIS library not fully loaded yet.");
|
||||
alert("Google Identity Services library not ready. Please try again in a moment.");
|
||||
gisContainer.innerHTML = '<p style="color: red;">GIS library not ready.</p>';
|
||||
if (setupGisBtn) setupGisBtn.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the credential response from the Google Sign-In library.
|
||||
* @param {!CredentialResponse} response The credential response object from GIS.
|
||||
* @param {string} toolId The ID of the tool.
|
||||
* @param {string} authProfileName The name of the auth service in tools file.
|
||||
*/
|
||||
function handleCredentialResponse(response, toolId, authProfileName) {
|
||||
console.debug("handleCredentialResponse called with:", { response, toolId, authProfileName });
|
||||
const headersTextarea = document.getElementById(`headers-textarea-${toolId}`);
|
||||
if (!headersTextarea) {
|
||||
console.error('Headers textarea not found for toolId:', toolId);
|
||||
return;
|
||||
}
|
||||
|
||||
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
|
||||
const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .setup-gis-btn`);
|
||||
const gisContainer = document.getElementById(`gisContainer-${UNIQUE_ID_BASE}`);
|
||||
|
||||
if (response.credential) {
|
||||
const idToken = response.credential;
|
||||
|
||||
try {
|
||||
let currentHeaders = {};
|
||||
if (headersTextarea.value) {
|
||||
currentHeaders = JSON.parse(headersTextarea.value);
|
||||
}
|
||||
const HEADER_KEY = `${authProfileName}_token`;
|
||||
currentHeaders[HEADER_KEY] = `${idToken}`;
|
||||
headersTextarea.value = JSON.stringify(currentHeaders, null, 2);
|
||||
|
||||
if (gisContainer) gisContainer.style.display = 'none';
|
||||
if (setupGisBtn) setupGisBtn.style.display = '';
|
||||
|
||||
} catch (e) {
|
||||
alert('Headers are not valid JSON. Please correct and try again.');
|
||||
console.error("Header JSON parse error:", e);
|
||||
}
|
||||
} else {
|
||||
console.error("Error: No credential in response", response);
|
||||
alert('Error: No ID Token received. Check console for details.');
|
||||
|
||||
if (gisContainer) gisContainer.style.display = 'none';
|
||||
if (setupGisBtn) setupGisBtn.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
// creates the Google Auth method dropdown
|
||||
export function createGoogleAuthMethodItem(toolId, authProfileName) {
|
||||
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
|
||||
const item = document.createElement('div');
|
||||
|
||||
item.className = 'auth-method-item';
|
||||
item.innerHTML = `
|
||||
<div class="auth-method-header">
|
||||
<span class="auth-method-label">Google ID Token (${authProfileName})</span>
|
||||
<button class="toggle-details-tab">Auto Setup</button>
|
||||
</div>
|
||||
<div class="auth-method-details" id="google-auth-details-${UNIQUE_ID_BASE}" style="display: none;">
|
||||
<div class="auth-controls">
|
||||
<div class="auth-input-row">
|
||||
<label for="clientIdInput-${UNIQUE_ID_BASE}">OAuth Client ID:</label>
|
||||
<input type="text" id="clientIdInput-${UNIQUE_ID_BASE}" placeholder="Enter Client ID" class="auth-input">
|
||||
</div>
|
||||
<div class="auth-instructions">
|
||||
<strong>Action Required:</strong> Add this URL (e.g., http://localhost:PORT) to the Client ID's <strong>Authorized JavaScript origins</strong> to avoid a 401 error. If using Google OAuth,
|
||||
navigate to <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a> for this setting.
|
||||
</div>
|
||||
<div class="auth-method-actions">
|
||||
<button class="btn btn--setup-gis">Continue</button>
|
||||
<div id="gisContainer-${UNIQUE_ID_BASE}" class="auth-interactive-element gis-container" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const toggleBtn = item.querySelector('.toggle-details-tab');
|
||||
const detailsDiv = item.querySelector(`#google-auth-details-${UNIQUE_ID_BASE}`);
|
||||
const setupGisBtn = item.querySelector('.btn--setup-gis');
|
||||
const clientIdInput = item.querySelector(`#clientIdInput-${UNIQUE_ID_BASE}`);
|
||||
const gisContainer = item.querySelector(`#gisContainer-${UNIQUE_ID_BASE}`);
|
||||
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
const isVisible = detailsDiv.style.display === 'flex';
|
||||
detailsDiv.style.display = isVisible ? 'none' : 'flex';
|
||||
toggleBtn.textContent = isVisible ? 'Auto Setup' : 'Close';
|
||||
if (!isVisible) {
|
||||
if (gisContainer) {
|
||||
gisContainer.innerHTML = '';
|
||||
gisContainer.style.display = 'none';
|
||||
}
|
||||
if (setupGisBtn) {
|
||||
setupGisBtn.style.display = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setupGisBtn.addEventListener('click', () => {
|
||||
const clientId = clientIdInput.value;
|
||||
if (!clientId) {
|
||||
alert('Please enter an OAuth Client ID first.');
|
||||
return;
|
||||
}
|
||||
renderGoogleSignInButton(toolId, clientId, authProfileName);
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -128,6 +128,7 @@ async function fetchToolDetails(toolName, toolDisplayArea) {
|
||||
id: toolName,
|
||||
name: toolName,
|
||||
description: toolObject.description || "No description provided.",
|
||||
authRequired: toolObject.authRequired || [],
|
||||
parameters: (toolObject.parameters || []).map(param => {
|
||||
let inputType = 'text';
|
||||
const apiType = param.type ? param.type.toLowerCase() : 'string';
|
||||
|
||||
@@ -35,6 +35,9 @@ function renderNavbar(containerId, activePath) {
|
||||
<li><a href="/ui/tools">Tools</a></li>
|
||||
<li><a href="/ui/toolsets">Toolsets</a></li>
|
||||
</ul>
|
||||
<div class="upload-yaml-tab">
|
||||
<a href="/ui/upload" class="btn btn--yaml-tab">YAML Builder</a>
|
||||
</div>
|
||||
</nav>
|
||||
`;
|
||||
|
||||
@@ -49,5 +52,15 @@ function renderNavbar(containerId, activePath) {
|
||||
link.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
const yamlButton = navbarContainer.querySelector('.btn--yaml-tab');
|
||||
if (yamlButton) {
|
||||
const buttonPath = new URL(yamlButton.href).pathname;
|
||||
if (buttonPath === activePath) {
|
||||
yamlButton.classList.add('active');
|
||||
} else {
|
||||
yamlButton.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
internal/server/static/js/sourceSelector.js
Normal file
91
internal/server/static/js/sourceSelector.js
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const sourceTypeSelect = document.getElementById('sourceTypeSelect');
|
||||
const yamlOutputTextarea = document.getElementById('yamlOutput');
|
||||
let dbConfigData = {};
|
||||
|
||||
const jsonPath = '/ui/data/source_templates.json';
|
||||
|
||||
async function loadDbConfigs() {
|
||||
try {
|
||||
const response = await fetch(jsonPath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status} fetching ${jsonPath}`);
|
||||
}
|
||||
dbConfigData = await response.json();
|
||||
populateDropdown();
|
||||
} catch (error) {
|
||||
console.error('Could not load database types:', error);
|
||||
sourceTypeSelect.disabled = true;
|
||||
let errorOption = sourceTypeSelect.querySelector('option[value=""]');
|
||||
if (errorOption) {
|
||||
errorOption.textContent = 'Failed to load types';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function populateDropdown() {
|
||||
if (!dbConfigData || Object.keys(dbConfigData).length === 0) {
|
||||
console.warn('No database types found in JSON data.');
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(dbConfigData).sort().forEach(dbType => {
|
||||
const option = document.createElement('option');
|
||||
option.value = dbType;
|
||||
option.textContent = dbType;
|
||||
sourceTypeSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
sourceTypeSelect.addEventListener('change', () => {
|
||||
const selectedType = sourceTypeSelect.value;
|
||||
if (!selectedType || !dbConfigData[selectedType]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fields = dbConfigData[selectedType];
|
||||
if (!Array.isArray(fields)) {
|
||||
console.error(`Fields for ${selectedType} is not an array.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentYaml = yamlOutputTextarea.value;
|
||||
const lines = currentYaml.split('\n');
|
||||
const sourcesIndex = lines.findIndex(line => line.trim() === 'sources:');
|
||||
if (sourcesIndex === -1) {
|
||||
alert('The line "sources:" was not found in the YAML content. Please add it to insert a source.');
|
||||
sourceTypeSelect.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const NAME_INDENT = ' ';
|
||||
const FIELD_INDENT = ' ';
|
||||
|
||||
let snippetLines = [];
|
||||
snippetLines.push(`${NAME_INDENT}YOUR_${selectedType.toUpperCase()}_SOURCE_NAME:`);
|
||||
fields.forEach(field => {
|
||||
snippetLines.push(`${FIELD_INDENT}${field}:`);
|
||||
});
|
||||
|
||||
lines.splice(sourcesIndex + 1, 0, ...snippetLines);
|
||||
yamlOutputTextarea.value = lines.join('\n');
|
||||
yamlOutputTextarea.scrollTop = 0;
|
||||
sourceTypeSelect.value = "";
|
||||
});
|
||||
|
||||
loadDbConfigs();
|
||||
});
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { handleRunTool, displayResults } from './runTool.js';
|
||||
import { createGoogleAuthMethodItem } from './auth.js'
|
||||
|
||||
/**
|
||||
* Helper function to create form inputs for parameters.
|
||||
@@ -151,7 +152,7 @@ function createParamInput(param, toolId) {
|
||||
* parsed. The function receives the updated headers object as its argument.
|
||||
* @return {!HTMLDivElement} The outermost div element of the created modal.
|
||||
*/
|
||||
function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
|
||||
function createHeaderEditorModal(toolId, currentHeaders, toolParameters, authRequired, saveCallback) {
|
||||
const MODAL_ID = `header-modal-${toolId}`;
|
||||
let modal = document.getElementById(MODAL_ID);
|
||||
|
||||
@@ -174,9 +175,37 @@ function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
|
||||
headersTextarea.rows = 10;
|
||||
headersTextarea.value = JSON.stringify(currentHeaders, null, 2);
|
||||
|
||||
// handle authenticated params
|
||||
const authProfileNames = new Set();
|
||||
toolParameters.forEach(param => {
|
||||
const isAuthParam = param.authServices && param.authServices.length > 0;
|
||||
if (isAuthParam && param.authServices) {
|
||||
param.authServices.forEach(name => authProfileNames.add(name));
|
||||
}
|
||||
});
|
||||
|
||||
// handle authorized invocations
|
||||
if (authRequired && authRequired.length > 0) {
|
||||
authRequired.forEach(name => authProfileNames.add(name));
|
||||
}
|
||||
|
||||
modalContent.appendChild(modalHeader);
|
||||
modalContent.appendChild(headersTextarea);
|
||||
|
||||
if (authProfileNames.size > 0 || authRequired.length > 0) {
|
||||
const authHelperSection = document.createElement('div');
|
||||
authHelperSection.className = 'auth-helper-section';
|
||||
const authList = document.createElement('div');
|
||||
authList.className = 'auth-method-list';
|
||||
|
||||
authProfileNames.forEach(profileName => {
|
||||
const authItem = createGoogleAuthMethodItem(toolId, profileName);
|
||||
authList.appendChild(authItem);
|
||||
});
|
||||
authHelperSection.appendChild(authList);
|
||||
modalContent.appendChild(authHelperSection);
|
||||
}
|
||||
|
||||
const modalActions = document.createElement('div');
|
||||
const closeButton = document.createElement('button');
|
||||
const saveButton = document.createElement('button');
|
||||
@@ -205,13 +234,6 @@ function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
|
||||
modalContent.appendChild(authTokenDropdown);
|
||||
modal.appendChild(modalContent);
|
||||
|
||||
// Close modal if clicked outside
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === modal) {
|
||||
closeHeaderEditor(toolId);
|
||||
}
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
@@ -246,7 +268,7 @@ function createAuthTokenInfoDropdown() {
|
||||
|
||||
details.className = 'auth-token-details';
|
||||
details.appendChild(summary);
|
||||
summary.textContent = 'How to extract Google OAuth ID Token';
|
||||
summary.textContent = 'How to extract Google OAuth ID Token manually';
|
||||
content.className = 'auth-token-content';
|
||||
|
||||
// auth instruction dropdown
|
||||
@@ -321,10 +343,9 @@ export function renderToolInterface(tool, containerElement) {
|
||||
const updateLastResults = (newResults) => {
|
||||
lastResults = newResults;
|
||||
};
|
||||
|
||||
const updateCurrentHeaders = (newHeaders) => {
|
||||
currentHeaders = newHeaders;
|
||||
const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, updateCurrentHeaders);
|
||||
const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
|
||||
containerElement.appendChild(newModal);
|
||||
};
|
||||
|
||||
@@ -428,7 +449,7 @@ export function renderToolInterface(tool, containerElement) {
|
||||
containerElement.appendChild(responseContainer);
|
||||
|
||||
// create and append the header editor modal
|
||||
const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, updateCurrentHeaders);
|
||||
const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
|
||||
containerElement.appendChild(headerModal);
|
||||
|
||||
prettifyCheckbox.addEventListener('change', () => {
|
||||
|
||||
184
internal/server/static/js/uploadYaml.js
Normal file
184
internal/server/static/js/uploadYaml.js
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const SESSION_STORAGE_KEY = 'yamlUploaderSessionData';
|
||||
const yamlFileInput = document.getElementById('yamlFileInput');
|
||||
const fileError = document.getElementById('fileError');
|
||||
const yamlOutput = document.getElementById('yamlOutput');
|
||||
const clearSessionButton = document.getElementById('clearSessionButton');
|
||||
const loadedFileNameDisplay = document.getElementById('loadedFileName');
|
||||
const downloadYamlButton = document.getElementById('downloadYamlButton');
|
||||
|
||||
function debounce(func, delay) {
|
||||
let timeoutId;
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
function saveToSessionStorage(filename, yamlContent) {
|
||||
try {
|
||||
const data = { filename: filename, yamlContent: yamlContent };
|
||||
sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(data));
|
||||
console.debug('Saved to sessionStorage');
|
||||
} catch (e) {
|
||||
console.error("Error saving to sessionStorage:", e);
|
||||
fileError.textContent = "Failed to save data to session storage.";
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromSessionStorage() {
|
||||
try {
|
||||
const storedData = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
if (storedData) {
|
||||
const data = JSON.parse(storedData);
|
||||
if (data.yamlContent) {
|
||||
yamlOutput.value = data.yamlContent;
|
||||
}
|
||||
if (data.filename) {
|
||||
loadedFileNameDisplay.textContent = `Current file: ${data.filename}`;
|
||||
} else {
|
||||
loadedFileNameDisplay.textContent = "";
|
||||
}
|
||||
console.debug('Loaded from sessionStorage');
|
||||
} else {
|
||||
console.debug('No data in sessionStorage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading from sessionStorage:", e);
|
||||
fileError.textContent = "Failed to load data from session storage.";
|
||||
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
function clearSessionStorage() {
|
||||
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
yamlOutput.value = '';
|
||||
fileError.textContent = '';
|
||||
yamlFileInput.value = '';
|
||||
loadedFileNameDisplay.textContent = '';
|
||||
console.log("Cleared stored YAML from sessionStorage.");
|
||||
}
|
||||
|
||||
if (yamlFileInput) {
|
||||
yamlFileInput.addEventListener('change', (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) {
|
||||
console.log('No file selected.');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = file.name;
|
||||
if (!fileName.toLowerCase().endsWith('.yaml') && !fileName.toLowerCase().endsWith('.yml')) {
|
||||
fileError.textContent = 'Invalid file type. Please upload a .yaml or .yml file.';
|
||||
yamlFileInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
fileError.textContent = '';
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (e) => {
|
||||
const content = e.target.result;
|
||||
try {
|
||||
if (typeof jsyaml === 'undefined') {
|
||||
fileError.textContent = 'Error: js-yaml library not loaded.';
|
||||
return;
|
||||
}
|
||||
const parsedYaml = jsyaml.load(content);
|
||||
const prettyYaml = jsyaml.dump(parsedYaml, { indent: 2, lineWidth: 120, noRefs: true });
|
||||
|
||||
yamlOutput.value = prettyYaml;
|
||||
loadedFileNameDisplay.textContent = `Current file: ${fileName}`;
|
||||
saveToSessionStorage(fileName, prettyYaml);
|
||||
|
||||
} catch (err) {
|
||||
fileError.textContent = 'Error parsing YAML: ' + err.message;
|
||||
console.error('YAML Parsing Error:', err);
|
||||
yamlOutput.value = '';
|
||||
loadedFileNameDisplay.textContent = '';
|
||||
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
}
|
||||
};
|
||||
reader.onerror = () => {
|
||||
fileError.textContent = 'Error reading file.';
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
if (yamlOutput) {
|
||||
yamlOutput.addEventListener('input', debounce(() => {
|
||||
const currentFilename = getDownloadFilename();
|
||||
saveToSessionStorage(currentFilename, yamlOutput.value);
|
||||
console.debug("YAML content changes saved to sessionStorage.");
|
||||
}, 500));
|
||||
}
|
||||
|
||||
if (clearSessionButton) {
|
||||
clearSessionButton.addEventListener('click', () => {
|
||||
if (confirm("Clear all content from the YAML editor? This cannot be undone.")) {
|
||||
clearSessionStorage();
|
||||
console.debug("User confirmed clearing session.");
|
||||
} else {
|
||||
console.debug("User cancelled clearing session.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getDownloadFilename() {
|
||||
const loadedFileName = loadedFileNameDisplay.textContent;
|
||||
let baseName = 'tools';
|
||||
if (loadedFileName && loadedFileName.startsWith('Current file: ')) {
|
||||
const originalFileName = loadedFileName.substring('Current file: '.length);
|
||||
baseName = originalFileName.replace(/\.ya?ml$/i, '');
|
||||
}
|
||||
return `${baseName}_MODIFIED.yaml`;
|
||||
}
|
||||
|
||||
if (downloadYamlButton) {
|
||||
downloadYamlButton.addEventListener('click', () => {
|
||||
const yamlContent = yamlOutput.value;
|
||||
if (!yamlContent) {
|
||||
alert("Text area is empty. Nothing to download.");
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = getDownloadFilename();
|
||||
|
||||
const blob = new Blob([yamlContent], { type: 'application/octet-stream' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
console.debug('Download link cleaned up');
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
loadFromSessionStorage();
|
||||
});
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tools View</title>
|
||||
<link rel="stylesheet" href="/ui/css/style.css">
|
||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar-container" data-active-nav="/ui/tools"></div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>Toolsets View</title>
|
||||
<link rel="stylesheet" href="/ui/css/style.css">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar-container" data-active-nav="/ui/toolsets"></div>
|
||||
|
||||
69
internal/server/static/uploadYaml.html
Normal file
69
internal/server/static/uploadYaml.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>YAML Builder View</title>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/ui/css/style.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar-container" data-active-nav="/ui/upload"></div>
|
||||
<div id="main-content-container">
|
||||
<div class="editor-container">
|
||||
<div id="yaml-editor-area">
|
||||
<div id="yaml-uploader">
|
||||
<h3>Upload YAML File</h3>
|
||||
<p>Please select a file ending with .yaml or .yml</p>
|
||||
<div id="controls">
|
||||
<div>
|
||||
<input type="file" id="yamlFileInput" accept=".yaml,.yml" class="hidden-file-input">
|
||||
<label for="yamlFileInput" class="btn btn--run">Choose File</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="fileError"></div>
|
||||
<div id="loadedFileName"></div>
|
||||
</div>
|
||||
<div class="yaml-output-container">
|
||||
<div class="editor-header">
|
||||
<h3>Formatted YAML Output:</h3>
|
||||
<div class="header-actions">
|
||||
<button id="clearSessionButton" class="icon-button" title="Clear YAML">
|
||||
<i class="material-icons">delete</i>
|
||||
</button>
|
||||
<button id="downloadYamlButton" class="icon-button" title="Download YAML">
|
||||
<i class="material-icons">file_download</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="yamlOutput" placeholder="Upload a YAML file or paste content here..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="source-adder">
|
||||
<h3>Add Source Snippet</h3>
|
||||
<select id="sourceTypeSelect">
|
||||
<option value="">Select DB Type...</option>
|
||||
</select>
|
||||
<p style="font-size: 0.8em; color: #666;">Select a database type to append a template to the YAML output.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/ui/js/navbar.js"></script>
|
||||
<script src="/ui/js/uploadYaml.js"></script>
|
||||
<script src="/ui/js/sourceSelector.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
const activeNav = navbarContainer.getAttribute('data-active-nav');
|
||||
if (typeof renderNavbar === 'function') {
|
||||
renderNavbar('navbar-container', activeNav);
|
||||
} else {
|
||||
console.error('renderNavbar function not found.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -24,6 +24,7 @@ func webRouter() (chi.Router, error) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/index.html") })
|
||||
r.Get("/tools", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/tools.html") })
|
||||
r.Get("/toolsets", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/toolsets.html") })
|
||||
r.Get("/upload", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/uploadYaml.html") })
|
||||
|
||||
// handler for all other static files/assets
|
||||
staticFS, _ := fs.Sub(staticContent, "static")
|
||||
|
||||
@@ -73,6 +73,20 @@ func TestWebEndpoint(t *testing.T) {
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "Toolsets View",
|
||||
},
|
||||
{
|
||||
name: "web yaml builder page",
|
||||
path: "/ui/upload",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "YAML Builder View",
|
||||
},
|
||||
{
|
||||
name: "web yaml builder page with trailing slash",
|
||||
path: "/ui/upload/",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "text/html",
|
||||
wantPageTitle: "YAML Builder View",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package duckdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
_ "github.com/marcboeker/go-duckdb/v2"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const SourceKind string = "duckdb"
|
||||
|
||||
func init() {
|
||||
if !sources.Register(SourceKind, newConfig) {
|
||||
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
|
||||
}
|
||||
}
|
||||
|
||||
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
|
||||
actual := Config{Name: name}
|
||||
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
Db *sql.DB
|
||||
}
|
||||
|
||||
// SourceKind implements sources.Source.
|
||||
func (s *Source) SourceKind() string {
|
||||
return SourceKind
|
||||
}
|
||||
|
||||
func (s *Source) DuckDb() *sql.DB {
|
||||
return s.Db
|
||||
}
|
||||
|
||||
// validate Source
|
||||
var _ sources.Source = &Source{}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
DatabaseFile string `yaml:"dbFilePath,omitempty"`
|
||||
Configuration map[string]string `yaml:"configuration,omitempty"`
|
||||
}
|
||||
|
||||
func (r Config) SourceConfigKind() string {
|
||||
return SourceKind
|
||||
}
|
||||
|
||||
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||
db, err := initDuckDbConnection(ctx, tracer, r.Name, r.DatabaseFile, r.Configuration)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create db connection: %w", err)
|
||||
}
|
||||
|
||||
err = db.PingContext(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to connect sucessfully: %w", err)
|
||||
}
|
||||
|
||||
s := &Source{
|
||||
Name: r.Name,
|
||||
Kind: r.Kind,
|
||||
Db: db,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ sources.SourceConfig = Config{}
|
||||
|
||||
func initDuckDbConnection(ctx context.Context, tracer trace.Tracer, name string, dbFilePath string, duckdbConfiguration map[string]string) (*sql.DB, error) {
|
||||
//nolint:all // Reassigned ctx
|
||||
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
|
||||
defer span.End()
|
||||
|
||||
var configStr = getDuckDbConfiguration(dbFilePath, duckdbConfiguration)
|
||||
|
||||
//Open database connection
|
||||
db, err := sql.Open("duckdb", configStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open duckdb connection: %w", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func getDuckDbConfiguration(dbFilePath string, duckdbConfiguration map[string]string) string {
|
||||
if len(duckdbConfiguration) == 0 {
|
||||
return dbFilePath
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
for key, value := range duckdbConfiguration {
|
||||
params.Set(key, value)
|
||||
}
|
||||
|
||||
var configStr strings.Builder
|
||||
configStr.WriteString(dbFilePath)
|
||||
configStr.WriteString("?")
|
||||
configStr.WriteString(params.Encode())
|
||||
|
||||
return configStr.String()
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package duckdb_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources/duckdb"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
)
|
||||
|
||||
func TestParserFromYamlDuckDb(t *testing.T) {
|
||||
config := make(map[string]string)
|
||||
config["access_mode"] = "READ_ONLY"
|
||||
config["threads"] = "4"
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.SourceConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
sources:
|
||||
my-duckdb:
|
||||
kind: duckdb
|
||||
`,
|
||||
want: server.SourceConfigs{
|
||||
"my-duckdb": duckdb.Config{
|
||||
Name: "my-duckdb",
|
||||
Kind: duckdb.SourceKind,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with custom configuration",
|
||||
in: `
|
||||
sources:
|
||||
my-duckdb:
|
||||
kind: duckdb
|
||||
configuration:
|
||||
access_mode: READ_ONLY
|
||||
threads: 4
|
||||
`,
|
||||
want: server.SourceConfigs{
|
||||
"my-duckdb": duckdb.Config{
|
||||
Name: "my-duckdb",
|
||||
Kind: duckdb.SourceKind,
|
||||
Configuration: config,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Sources server.SourceConfigs `yaml:"sources"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if !cmp.Equal(tc.want, got.Sources) {
|
||||
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package duckdbsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources/duckdb"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
)
|
||||
|
||||
const kind string = "duckdb-sql"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||
actual := Config{Name: name}
|
||||
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
type compatibleSource interface {
|
||||
DuckDb() *sql.DB
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &duckdb.Source{}
|
||||
var compatibleSources = [...]string{duckdb.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
Statement string `yaml:"statement" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
TemplateParameters tools.Parameters `yaml:"templateParameters"`
|
||||
}
|
||||
|
||||
// Initialize implements tools.ToolConfig.
|
||||
func (c Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[c.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", c.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(c.TemplateParameters, c.Parameters)
|
||||
|
||||
mcpManifest := tools.McpManifest{
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
InputSchema: paramMcpManifest,
|
||||
}
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: c.Name,
|
||||
Kind: kind,
|
||||
Parameters: c.Parameters,
|
||||
TemplateParameters: c.TemplateParameters,
|
||||
AllParams: allParameters,
|
||||
Statement: c.Statement,
|
||||
AuthRequired: c.AuthRequired,
|
||||
Db: s.DuckDb(),
|
||||
manifest: tools.Manifest{Description: c.Description, Parameters: paramManifest, AuthRequired: c.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ToolConfigKind implements tools.ToolConfig.
|
||||
func (c Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
TemplateParameters tools.Parameters `yaml:"templateParameters"`
|
||||
AllParams tools.Parameters `yaml:"allParams"`
|
||||
|
||||
Db *sql.DB
|
||||
Statement string `yaml:"statement"`
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
// Authorized implements tools.Tool.
|
||||
func (t Tool) Authorized(verifiedAuthSources []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthSources)
|
||||
}
|
||||
|
||||
// Invoke implements tools.Tool.
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
|
||||
paramsMap := params.AsMap()
|
||||
newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract template params %w", err)
|
||||
}
|
||||
|
||||
newParams, err := tools.GetParams(t.Parameters, paramsMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract standard params %w", err)
|
||||
}
|
||||
|
||||
sliceParams := newParams.AsSlice()
|
||||
// Execute the SQL query with parameters
|
||||
rows, err := t.Db.QueryContext(ctx, newStatement, sliceParams...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to execute query: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// Get column names
|
||||
cols, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get column names: %w", err)
|
||||
}
|
||||
|
||||
values := make([]any, len(cols))
|
||||
valuePtrs := make([]any, len(cols))
|
||||
for i := range values {
|
||||
valuePtrs[i] = &values[i]
|
||||
}
|
||||
|
||||
// Prepare the result slice
|
||||
var result []any
|
||||
// Iterate through the rows
|
||||
for rows.Next() {
|
||||
// Scan the row into the value pointers
|
||||
if err := rows.Scan(valuePtrs...); err != nil {
|
||||
return nil, fmt.Errorf("unable to scan row: %w", err)
|
||||
}
|
||||
|
||||
// Create a map for this row
|
||||
rowMap := make(map[string]interface{})
|
||||
for i, col := range cols {
|
||||
val := values[i]
|
||||
// Handle nil values
|
||||
if val == nil {
|
||||
rowMap[col] = nil
|
||||
continue
|
||||
}
|
||||
// Store the value in the map
|
||||
rowMap[col] = val
|
||||
}
|
||||
result = append(result, rowMap)
|
||||
}
|
||||
|
||||
if err = rows.Close(); err != nil {
|
||||
return nil, fmt.Errorf("unable to close rows: %w", err)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating rows: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Manifest implements tools.Tool.
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
// McpManifest implements tools.Tool.
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
// ParseParams implements tools.Tool.
|
||||
func (t Tool) ParseParams(data map[string]any, claimsMap map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.AllParams, data, claimsMap)
|
||||
}
|
||||
|
||||
var _ tools.Tool = Tool{}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package duckdbsql_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/duckdbsql"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
)
|
||||
|
||||
func TestParseFromYamlDuckDb(t *testing.T) {
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: duckdb-sql
|
||||
source: my-duckdb-instance
|
||||
description: some description
|
||||
statement: |
|
||||
select * from hotel WHERE name = $hotel;
|
||||
parameters:
|
||||
- name: hotel
|
||||
type: string
|
||||
description: hotel parameter description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": duckdbsql.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "duckdb-sql",
|
||||
Source: "my-duckdb-instance",
|
||||
Description: "some description",
|
||||
Statement: "select * from hotel WHERE name = $hotel;\n",
|
||||
AuthRequired: []string{},
|
||||
Parameters: []tools.Parameter{
|
||||
tools.NewStringParameter("hotel", "hotel parameter description"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
|
||||
// Create a context with a logger
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create context with logger: %s", err)
|
||||
}
|
||||
|
||||
// Parse contents with context
|
||||
err = yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package duckdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
"github.com/googleapis/genai-toolbox/tests"
|
||||
)
|
||||
|
||||
var (
|
||||
DuckDbKind = "duckdb-sql"
|
||||
dbPath = "/tmp/users.db"
|
||||
)
|
||||
|
||||
func getDuckDbVars() map[string]any {
|
||||
return map[string]any{
|
||||
"kind": "duckdb",
|
||||
"dbFilePath": dbPath,
|
||||
"configuration": map[string]any{
|
||||
"access_mode": "READ_WRITE",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setupDuckDb(t *testing.T, createParamStmt, insertParamStmt, createAuthStmt, insertAuthStmt string, params []any, authparams []any) {
|
||||
// Remove any existing database file to ensure a clean state
|
||||
os.Remove(dbPath)
|
||||
|
||||
// Open a connection to DuckDB
|
||||
db, err := sql.Open("duckdb", dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open DuckDB connection: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(createParamStmt, params...)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create table: %v", err)
|
||||
}
|
||||
_, err = db.Exec(createAuthStmt, authparams...)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create table: %v", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(insertParamStmt, params...)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert initial data: %v", err)
|
||||
}
|
||||
_, err = db.Exec(insertAuthStmt, authparams...)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create table: %v", err)
|
||||
}
|
||||
}
|
||||
func TestDuckDb(t *testing.T) {
|
||||
sourceConfig := getDuckDbVars()
|
||||
var args []string
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
|
||||
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := GetDuckDbParamToolInfo(tableNameParam)
|
||||
createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := GetDuckDbAuthToolInfo(tableNameAuth)
|
||||
setupDuckDb(t, createParamTableStmt, insertParamTableStmt, createAuthTableStmt, insertAuthTableStmt, paramTestParams, authTestParams)
|
||||
|
||||
toolsFile := tests.GetToolsConfig(sourceConfig, DuckDbKind, paramToolStmt, idParamToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
|
||||
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
|
||||
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, DuckDbKind, tmplSelectCombined, tmplSelectFilterCombined, "")
|
||||
defer os.Remove(dbPath)
|
||||
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
|
||||
if err != nil {
|
||||
t.Fatalf("command initialization returned an error: %s", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
|
||||
if err != nil {
|
||||
t.Logf("toolbox command logs: \n%s", out)
|
||||
t.Fatalf("toolbox didn't start successfully: %s", err)
|
||||
}
|
||||
|
||||
tests.RunToolGetTest(t)
|
||||
|
||||
select1Want, failInvocationWant, _ := GetDuckDbWants()
|
||||
|
||||
_, invokeParamWantNull, nullWant, _ := tests.GetNonSpannerInvokeParamWant()
|
||||
invokeParamWant := "[{\"name\":\"Alice\"},{\"name\":\"Sid\"}]"
|
||||
mcpInvokeParamWant := "{\"jsonrpc\":\"2.0\",\"id\":\"my-tool\",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"name\\\":\\\"Alice\\\"}\"},{\"type\":\"text\",\"text\":\"{\\\"name\\\":\\\"Sid\\\"}\"}]}}"
|
||||
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, nullWant, true, true)
|
||||
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
|
||||
templateParamTestConfig := tests.NewTemplateParameterTestConfig(
|
||||
tests.WithSelectAllWant("[{\"age\":21,\"id\":1,\"name\":\"Alex\"},{\"age\":100,\"id\":2,\"name\":\"Alice\"}]"),
|
||||
tests.WithSelect1Want("[{\"age\":21,\"id\":1,\"name\":\"Alex\"}]"),
|
||||
tests.WithReplaceNameFieldArray(`["name"]`),
|
||||
tests.WithReplaceNameColFilter("name"),
|
||||
tests.WithCreateColArray(`["id INT","name VARCHAR(20)","age INT"]`),
|
||||
tests.WithInsert1Want("[{\"Count\":1}]"),
|
||||
)
|
||||
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, templateParamTestConfig)
|
||||
}
|
||||
|
||||
func GetDuckDbParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
|
||||
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER PRIMARY KEY, name TEXT);", tableName)
|
||||
insertStatement := fmt.Sprintf("INSERT INTO %s (id, name) VALUES (1, $1), (2, $2), (3, $3), (4, $4);", tableName)
|
||||
toolStatement := fmt.Sprintf("SELECT * EXCLUDE (id) FROM %s WHERE id = $1 OR name = $2 order by id;", tableName)
|
||||
idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id IN (SELECT unnest(list_value($1)) AS id);", tableName)
|
||||
toolStatement2 := fmt.Sprintf("SELECT name FROM %s WHERE id = list_extract(list_value($1), 1);", tableName)
|
||||
arrayToolStatement := fmt.Sprintf("SELECT name FROM %s WHERE id = ANY($1) AND name = ANY($2) order by name;", tableName)
|
||||
params := []any{"Alice", "Jane", "Sid", nil}
|
||||
return createStatement, insertStatement, toolStatement, idParamStatement, toolStatement2, arrayToolStatement, params
|
||||
}
|
||||
|
||||
// GetDuckDbAuthToolInfo returns statements and param of my-auth-tool for duckdb-sql kind
|
||||
func GetDuckDbAuthToolInfo(tableName string) (string, string, string, []any) {
|
||||
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER PRIMARY KEY, name TEXT, email TEXT);", tableName)
|
||||
insertStatement := fmt.Sprintf("INSERT INTO %s (id, name, email) VALUES (1, $1, $2), (2, $3, $4)", tableName)
|
||||
toolStatement := fmt.Sprintf("SELECT name FROM %s WHERE email = $1;", tableName)
|
||||
params := []any{"Alice", tests.ServiceAccountEmail, "Jane", "janedoe@gmail.com"}
|
||||
return createStatement, insertStatement, toolStatement, params
|
||||
}
|
||||
|
||||
func GetDuckDbWants() (string, string, string) {
|
||||
select1Want := "[{\"1\":1}]"
|
||||
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: Parser Error: syntax error at or near \"SELEC\""}],"isError":true}}`
|
||||
createTableStatement := `"CREATE TABLE t (id SERIAL PRIMARY KEY, name TEXT)"`
|
||||
return select1Want, failInvocationWant, createTableStatement
|
||||
}
|
||||
Reference in New Issue
Block a user