This is page 50 of 59. Use http://codebase.md/googleapis/genai-toolbox?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .ci
│ ├── continuous.release.cloudbuild.yaml
│ ├── generate_release_table.sh
│ ├── integration.cloudbuild.yaml
│ ├── quickstart_test
│ │ ├── go.integration.cloudbuild.yaml
│ │ ├── js.integration.cloudbuild.yaml
│ │ ├── py.integration.cloudbuild.yaml
│ │ ├── run_go_tests.sh
│ │ ├── run_js_tests.sh
│ │ ├── run_py_tests.sh
│ │ └── setup_hotels_sample.sql
│ ├── test_with_coverage.sh
│ └── versioned.release.cloudbuild.yaml
├── .github
│ ├── auto-label.yaml
│ ├── blunderbuss.yml
│ ├── CODEOWNERS
│ ├── header-checker-lint.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── feature_request.yml
│ │ └── question.yml
│ ├── label-sync.yml
│ ├── labels.yaml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── release-please.yml
│ ├── renovate.json5
│ ├── sync-repo-settings.yaml
│ └── workflows
│ ├── cloud_build_failure_reporter.yml
│ ├── deploy_dev_docs.yaml
│ ├── deploy_previous_version_docs.yaml
│ ├── deploy_versioned_docs.yaml
│ ├── docs_deploy.yaml
│ ├── docs_preview_clean.yaml
│ ├── docs_preview_deploy.yaml
│ ├── lint.yaml
│ ├── schedule_reporter.yml
│ ├── sync-labels.yaml
│ └── tests.yaml
├── .gitignore
├── .gitmodules
├── .golangci.yaml
├── .hugo
│ ├── archetypes
│ │ └── default.md
│ ├── assets
│ │ ├── icons
│ │ │ └── logo.svg
│ │ └── scss
│ │ ├── _styles_project.scss
│ │ └── _variables_project.scss
│ ├── go.mod
│ ├── go.sum
│ ├── hugo.toml
│ ├── layouts
│ │ ├── _default
│ │ │ └── home.releases.releases
│ │ ├── index.llms-full.txt
│ │ ├── index.llms.txt
│ │ ├── partials
│ │ │ ├── hooks
│ │ │ │ └── head-end.html
│ │ │ ├── navbar-version-selector.html
│ │ │ ├── page-meta-links.html
│ │ │ └── td
│ │ │ └── render-heading.html
│ │ ├── robot.txt
│ │ └── shortcodes
│ │ ├── include.html
│ │ ├── ipynb.html
│ │ └── regionInclude.html
│ ├── package-lock.json
│ ├── package.json
│ └── static
│ ├── favicons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ └── favicon.ico
│ └── js
│ └── w3.js
├── CHANGELOG.md
├── cmd
│ ├── options_test.go
│ ├── options.go
│ ├── root_test.go
│ ├── root.go
│ └── version.txt
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DEVELOPER.md
├── Dockerfile
├── docs
│ └── en
│ ├── _index.md
│ ├── about
│ │ ├── _index.md
│ │ └── faq.md
│ ├── concepts
│ │ ├── _index.md
│ │ └── telemetry
│ │ ├── index.md
│ │ ├── telemetry_flow.png
│ │ └── telemetry_traces.png
│ ├── getting-started
│ │ ├── _index.md
│ │ ├── colab_quickstart.ipynb
│ │ ├── configure.md
│ │ ├── introduction
│ │ │ ├── _index.md
│ │ │ └── architecture.png
│ │ ├── local_quickstart_go.md
│ │ ├── local_quickstart_js.md
│ │ ├── local_quickstart.md
│ │ ├── mcp_quickstart
│ │ │ ├── _index.md
│ │ │ ├── inspector_tools.png
│ │ │ └── inspector.png
│ │ └── quickstart
│ │ ├── go
│ │ │ ├── adkgo
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── genAI
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── genkit
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── langchain
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── openAI
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ └── quickstart_test.go
│ │ ├── golden.txt
│ │ ├── js
│ │ │ ├── genAI
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ ├── genkit
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ ├── langchain
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ ├── llamaindex
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ └── quickstart.test.js
│ │ ├── python
│ │ │ ├── __init__.py
│ │ │ ├── adk
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ ├── core
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ ├── langchain
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ ├── llamaindex
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ └── quickstart_test.py
│ │ └── shared
│ │ ├── cloud_setup.md
│ │ ├── configure_toolbox.md
│ │ └── database_setup.md
│ ├── how-to
│ │ ├── _index.md
│ │ ├── connect_via_geminicli.md
│ │ ├── connect_via_mcp.md
│ │ ├── connect-ide
│ │ │ ├── _index.md
│ │ │ ├── alloydb_pg_admin_mcp.md
│ │ │ ├── alloydb_pg_mcp.md
│ │ │ ├── bigquery_mcp.md
│ │ │ ├── cloud_sql_mssql_admin_mcp.md
│ │ │ ├── cloud_sql_mssql_mcp.md
│ │ │ ├── cloud_sql_mysql_admin_mcp.md
│ │ │ ├── cloud_sql_mysql_mcp.md
│ │ │ ├── cloud_sql_pg_admin_mcp.md
│ │ │ ├── cloud_sql_pg_mcp.md
│ │ │ ├── firestore_mcp.md
│ │ │ ├── looker_mcp.md
│ │ │ ├── mssql_mcp.md
│ │ │ ├── mysql_mcp.md
│ │ │ ├── neo4j_mcp.md
│ │ │ ├── postgres_mcp.md
│ │ │ ├── spanner_mcp.md
│ │ │ └── sqlite_mcp.md
│ │ ├── deploy_docker.md
│ │ ├── deploy_gke.md
│ │ ├── deploy_toolbox.md
│ │ ├── export_telemetry.md
│ │ └── toolbox-ui
│ │ ├── edit-headers.gif
│ │ ├── edit-headers.png
│ │ ├── index.md
│ │ ├── optional-param-checked.png
│ │ ├── optional-param-unchecked.png
│ │ ├── run-tool.gif
│ │ ├── tools.png
│ │ └── toolsets.png
│ ├── reference
│ │ ├── _index.md
│ │ ├── cli.md
│ │ └── prebuilt-tools.md
│ ├── resources
│ │ ├── _index.md
│ │ ├── authServices
│ │ │ ├── _index.md
│ │ │ └── google.md
│ │ ├── sources
│ │ │ ├── _index.md
│ │ │ ├── alloydb-admin.md
│ │ │ ├── alloydb-pg.md
│ │ │ ├── bigquery.md
│ │ │ ├── bigtable.md
│ │ │ ├── cassandra.md
│ │ │ ├── clickhouse.md
│ │ │ ├── cloud-healthcare.md
│ │ │ ├── cloud-monitoring.md
│ │ │ ├── cloud-sql-admin.md
│ │ │ ├── cloud-sql-mssql.md
│ │ │ ├── cloud-sql-mysql.md
│ │ │ ├── cloud-sql-pg.md
│ │ │ ├── couchbase.md
│ │ │ ├── dataplex.md
│ │ │ ├── dgraph.md
│ │ │ ├── elasticsearch.md
│ │ │ ├── firebird.md
│ │ │ ├── firestore.md
│ │ │ ├── http.md
│ │ │ ├── looker.md
│ │ │ ├── mindsdb.md
│ │ │ ├── mongodb.md
│ │ │ ├── mssql.md
│ │ │ ├── mysql.md
│ │ │ ├── neo4j.md
│ │ │ ├── oceanbase.md
│ │ │ ├── oracle.md
│ │ │ ├── postgres.md
│ │ │ ├── redis.md
│ │ │ ├── serverless-spark.md
│ │ │ ├── singlestore.md
│ │ │ ├── spanner.md
│ │ │ ├── sqlite.md
│ │ │ ├── tidb.md
│ │ │ ├── trino.md
│ │ │ ├── valkey.md
│ │ │ └── yugabytedb.md
│ │ └── tools
│ │ ├── _index.md
│ │ ├── alloydb
│ │ │ ├── _index.md
│ │ │ ├── alloydb-create-cluster.md
│ │ │ ├── alloydb-create-instance.md
│ │ │ ├── alloydb-create-user.md
│ │ │ ├── alloydb-get-cluster.md
│ │ │ ├── alloydb-get-instance.md
│ │ │ ├── alloydb-get-user.md
│ │ │ ├── alloydb-list-clusters.md
│ │ │ ├── alloydb-list-instances.md
│ │ │ ├── alloydb-list-users.md
│ │ │ └── alloydb-wait-for-operation.md
│ │ ├── alloydbainl
│ │ │ ├── _index.md
│ │ │ └── alloydb-ai-nl.md
│ │ ├── bigquery
│ │ │ ├── _index.md
│ │ │ ├── bigquery-analyze-contribution.md
│ │ │ ├── bigquery-conversational-analytics.md
│ │ │ ├── bigquery-execute-sql.md
│ │ │ ├── bigquery-forecast.md
│ │ │ ├── bigquery-get-dataset-info.md
│ │ │ ├── bigquery-get-table-info.md
│ │ │ ├── bigquery-list-dataset-ids.md
│ │ │ ├── bigquery-list-table-ids.md
│ │ │ ├── bigquery-search-catalog.md
│ │ │ └── bigquery-sql.md
│ │ ├── bigtable
│ │ │ ├── _index.md
│ │ │ └── bigtable-sql.md
│ │ ├── cassandra
│ │ │ ├── _index.md
│ │ │ └── cassandra-cql.md
│ │ ├── clickhouse
│ │ │ ├── _index.md
│ │ │ ├── clickhouse-execute-sql.md
│ │ │ ├── clickhouse-list-databases.md
│ │ │ ├── clickhouse-list-tables.md
│ │ │ └── clickhouse-sql.md
│ │ ├── cloudhealthcare
│ │ │ ├── _index.md
│ │ │ ├── cloud-healthcare-fhir-fetch-page.md
│ │ │ ├── cloud-healthcare-fhir-patient-everything.md
│ │ │ ├── cloud-healthcare-fhir-patient-search.md
│ │ │ ├── cloud-healthcare-get-dataset.md
│ │ │ ├── cloud-healthcare-get-dicom-store-metrics.md
│ │ │ ├── cloud-healthcare-get-dicom-store.md
│ │ │ ├── cloud-healthcare-get-fhir-resource.md
│ │ │ ├── cloud-healthcare-get-fhir-store-metrics.md
│ │ │ ├── cloud-healthcare-get-fhir-store.md
│ │ │ ├── cloud-healthcare-list-dicom-stores.md
│ │ │ ├── cloud-healthcare-list-fhir-stores.md
│ │ │ ├── cloud-healthcare-retrieve-rendered-dicom-instance.md
│ │ │ ├── cloud-healthcare-search-dicom-instances.md
│ │ │ ├── cloud-healthcare-search-dicom-series.md
│ │ │ └── cloud-healthcare-search-dicom-studies.md
│ │ ├── cloudmonitoring
│ │ │ ├── _index.md
│ │ │ └── cloud-monitoring-query-prometheus.md
│ │ ├── cloudsql
│ │ │ ├── _index.md
│ │ │ ├── cloudsqlcreatedatabase.md
│ │ │ ├── cloudsqlcreateusers.md
│ │ │ ├── cloudsqlgetinstances.md
│ │ │ ├── cloudsqllistdatabases.md
│ │ │ ├── cloudsqllistinstances.md
│ │ │ ├── cloudsqlmssqlcreateinstance.md
│ │ │ ├── cloudsqlmysqlcreateinstance.md
│ │ │ ├── cloudsqlpgcreateinstances.md
│ │ │ └── cloudsqlwaitforoperation.md
│ │ ├── couchbase
│ │ │ ├── _index.md
│ │ │ └── couchbase-sql.md
│ │ ├── dataform
│ │ │ ├── _index.md
│ │ │ └── dataform-compile-local.md
│ │ ├── dataplex
│ │ │ ├── _index.md
│ │ │ ├── dataplex-lookup-entry.md
│ │ │ ├── dataplex-search-aspect-types.md
│ │ │ └── dataplex-search-entries.md
│ │ ├── dgraph
│ │ │ ├── _index.md
│ │ │ └── dgraph-dql.md
│ │ ├── elasticsearch
│ │ │ ├── _index.md
│ │ │ └── elasticsearch-esql.md
│ │ ├── firebird
│ │ │ ├── _index.md
│ │ │ ├── firebird-execute-sql.md
│ │ │ └── firebird-sql.md
│ │ ├── firestore
│ │ │ ├── _index.md
│ │ │ ├── firestore-add-documents.md
│ │ │ ├── firestore-delete-documents.md
│ │ │ ├── firestore-get-documents.md
│ │ │ ├── firestore-get-rules.md
│ │ │ ├── firestore-list-collections.md
│ │ │ ├── firestore-query-collection.md
│ │ │ ├── firestore-query.md
│ │ │ ├── firestore-update-document.md
│ │ │ └── firestore-validate-rules.md
│ │ ├── http
│ │ │ ├── _index.md
│ │ │ └── http.md
│ │ ├── looker
│ │ │ ├── _index.md
│ │ │ ├── looker-add-dashboard-element.md
│ │ │ ├── looker-conversational-analytics.md
│ │ │ ├── looker-create-project-file.md
│ │ │ ├── looker-delete-project-file.md
│ │ │ ├── looker-dev-mode.md
│ │ │ ├── looker-get-connection-databases.md
│ │ │ ├── looker-get-connection-schemas.md
│ │ │ ├── looker-get-connection-table-columns.md
│ │ │ ├── looker-get-connection-tables.md
│ │ │ ├── looker-get-connections.md
│ │ │ ├── looker-get-dashboards.md
│ │ │ ├── looker-get-dimensions.md
│ │ │ ├── looker-get-explores.md
│ │ │ ├── looker-get-filters.md
│ │ │ ├── looker-get-looks.md
│ │ │ ├── looker-get-measures.md
│ │ │ ├── looker-get-models.md
│ │ │ ├── looker-get-parameters.md
│ │ │ ├── looker-get-project-file.md
│ │ │ ├── looker-get-project-files.md
│ │ │ ├── looker-get-projects.md
│ │ │ ├── looker-health-analyze.md
│ │ │ ├── looker-health-pulse.md
│ │ │ ├── looker-health-vacuum.md
│ │ │ ├── looker-make-dashboard.md
│ │ │ ├── looker-make-look.md
│ │ │ ├── looker-query-sql.md
│ │ │ ├── looker-query-url.md
│ │ │ ├── looker-query.md
│ │ │ ├── looker-run-dashboard.md
│ │ │ ├── looker-run-look.md
│ │ │ └── looker-update-project-file.md
│ │ ├── mindsdb
│ │ │ ├── _index.md
│ │ │ ├── mindsdb-execute-sql.md
│ │ │ └── mindsdb-sql.md
│ │ ├── mongodb
│ │ │ ├── _index.md
│ │ │ ├── mongodb-aggregate.md
│ │ │ ├── mongodb-delete-many.md
│ │ │ ├── mongodb-delete-one.md
│ │ │ ├── mongodb-find-one.md
│ │ │ ├── mongodb-find.md
│ │ │ ├── mongodb-insert-many.md
│ │ │ ├── mongodb-insert-one.md
│ │ │ ├── mongodb-update-many.md
│ │ │ └── mongodb-update-one.md
│ │ ├── mssql
│ │ │ ├── _index.md
│ │ │ ├── mssql-execute-sql.md
│ │ │ ├── mssql-list-tables.md
│ │ │ └── mssql-sql.md
│ │ ├── mysql
│ │ │ ├── _index.md
│ │ │ ├── mysql-execute-sql.md
│ │ │ ├── mysql-list-active-queries.md
│ │ │ ├── mysql-list-table-fragmentation.md
│ │ │ ├── mysql-list-tables-missing-unique-indexes.md
│ │ │ ├── mysql-list-tables.md
│ │ │ └── mysql-sql.md
│ │ ├── neo4j
│ │ │ ├── _index.md
│ │ │ ├── neo4j-cypher.md
│ │ │ ├── neo4j-execute-cypher.md
│ │ │ └── neo4j-schema.md
│ │ ├── oceanbase
│ │ │ ├── _index.md
│ │ │ ├── oceanbase-execute-sql.md
│ │ │ └── oceanbase-sql.md
│ │ ├── oracle
│ │ │ ├── _index.md
│ │ │ ├── oracle-execute-sql.md
│ │ │ └── oracle-sql.md
│ │ ├── postgres
│ │ │ ├── _index.md
│ │ │ ├── postgres-execute-sql.md
│ │ │ ├── postgres-list-active-queries.md
│ │ │ ├── postgres-list-available-extensions.md
│ │ │ ├── postgres-list-installed-extensions.md
│ │ │ ├── postgres-list-schemas.md
│ │ │ ├── postgres-list-tables.md
│ │ │ ├── postgres-list-views.md
│ │ │ └── postgres-sql.md
│ │ ├── redis
│ │ │ ├── _index.md
│ │ │ └── redis.md
│ │ ├── serverless-spark
│ │ │ ├── _index.md
│ │ │ ├── serverless-spark-cancel-batch.md
│ │ │ ├── serverless-spark-get-batch.md
│ │ │ └── serverless-spark-list-batches.md
│ │ ├── singlestore
│ │ │ ├── _index.md
│ │ │ ├── singlestore-execute-sql.md
│ │ │ └── singlestore-sql.md
│ │ ├── spanner
│ │ │ ├── _index.md
│ │ │ ├── spanner-execute-sql.md
│ │ │ ├── spanner-list-tables.md
│ │ │ └── spanner-sql.md
│ │ ├── sqlite
│ │ │ ├── _index.md
│ │ │ ├── sqlite-execute-sql.md
│ │ │ └── sqlite-sql.md
│ │ ├── tidb
│ │ │ ├── _index.md
│ │ │ ├── tidb-execute-sql.md
│ │ │ └── tidb-sql.md
│ │ ├── trino
│ │ │ ├── _index.md
│ │ │ ├── trino-execute-sql.md
│ │ │ └── trino-sql.md
│ │ ├── utility
│ │ │ ├── _index.md
│ │ │ └── wait.md
│ │ ├── valkey
│ │ │ ├── _index.md
│ │ │ └── valkey.md
│ │ └── yuagbytedb
│ │ ├── _index.md
│ │ └── yugabytedb-sql.md
│ ├── samples
│ │ ├── _index.md
│ │ ├── alloydb
│ │ │ ├── _index.md
│ │ │ ├── ai-nl
│ │ │ │ ├── alloydb_ai_nl.ipynb
│ │ │ │ └── index.md
│ │ │ └── mcp_quickstart.md
│ │ ├── bigquery
│ │ │ ├── _index.md
│ │ │ ├── colab_quickstart_bigquery.ipynb
│ │ │ ├── local_quickstart.md
│ │ │ └── mcp_quickstart
│ │ │ ├── _index.md
│ │ │ ├── inspector_tools.png
│ │ │ └── inspector.png
│ │ └── looker
│ │ ├── _index.md
│ │ ├── looker_gemini_oauth
│ │ │ ├── _index.md
│ │ │ ├── authenticated.png
│ │ │ ├── authorize.png
│ │ │ └── registration.png
│ │ ├── looker_gemini.md
│ │ └── looker_mcp_inspector
│ │ ├── _index.md
│ │ ├── inspector_tools.png
│ │ └── inspector.png
│ └── sdks
│ ├── _index.md
│ ├── go-sdk.md
│ ├── js-sdk.md
│ └── python-sdk.md
├── gemini-extension.json
├── go.mod
├── go.sum
├── internal
│ ├── auth
│ │ ├── auth.go
│ │ └── google
│ │ └── google.go
│ ├── log
│ │ ├── handler.go
│ │ ├── log_test.go
│ │ ├── log.go
│ │ └── logger.go
│ ├── prebuiltconfigs
│ │ ├── prebuiltconfigs_test.go
│ │ ├── prebuiltconfigs.go
│ │ └── tools
│ │ ├── alloydb-postgres-admin.yaml
│ │ ├── alloydb-postgres-observability.yaml
│ │ ├── alloydb-postgres.yaml
│ │ ├── bigquery.yaml
│ │ ├── clickhouse.yaml
│ │ ├── cloud-healthcare.yaml
│ │ ├── cloud-sql-mssql-admin.yaml
│ │ ├── cloud-sql-mssql-observability.yaml
│ │ ├── cloud-sql-mssql.yaml
│ │ ├── cloud-sql-mysql-admin.yaml
│ │ ├── cloud-sql-mysql-observability.yaml
│ │ ├── cloud-sql-mysql.yaml
│ │ ├── cloud-sql-postgres-admin.yaml
│ │ ├── cloud-sql-postgres-observability.yaml
│ │ ├── cloud-sql-postgres.yaml
│ │ ├── dataplex.yaml
│ │ ├── elasticsearch.yaml
│ │ ├── firestore.yaml
│ │ ├── looker-conversational-analytics.yaml
│ │ ├── looker.yaml
│ │ ├── mindsdb.yaml
│ │ ├── mssql.yaml
│ │ ├── mysql.yaml
│ │ ├── neo4j.yaml
│ │ ├── oceanbase.yaml
│ │ ├── postgres.yaml
│ │ ├── serverless-spark.yaml
│ │ ├── singlestore.yaml
│ │ ├── spanner-postgres.yaml
│ │ ├── spanner.yaml
│ │ └── sqlite.yaml
│ ├── server
│ │ ├── api_test.go
│ │ ├── api.go
│ │ ├── common_test.go
│ │ ├── config.go
│ │ ├── mcp
│ │ │ ├── jsonrpc
│ │ │ │ ├── jsonrpc_test.go
│ │ │ │ └── jsonrpc.go
│ │ │ ├── mcp.go
│ │ │ ├── util
│ │ │ │ └── lifecycle.go
│ │ │ ├── v20241105
│ │ │ │ ├── method.go
│ │ │ │ └── types.go
│ │ │ ├── v20250326
│ │ │ │ ├── method.go
│ │ │ │ └── types.go
│ │ │ └── v20250618
│ │ │ ├── method.go
│ │ │ └── types.go
│ │ ├── mcp_test.go
│ │ ├── mcp.go
│ │ ├── server_test.go
│ │ ├── server.go
│ │ ├── static
│ │ │ ├── assets
│ │ │ │ └── mcptoolboxlogo.png
│ │ │ ├── css
│ │ │ │ └── style.css
│ │ │ ├── index.html
│ │ │ ├── js
│ │ │ │ ├── auth.js
│ │ │ │ ├── loadTools.js
│ │ │ │ ├── mainContent.js
│ │ │ │ ├── navbar.js
│ │ │ │ ├── runTool.js
│ │ │ │ ├── toolDisplay.js
│ │ │ │ ├── tools.js
│ │ │ │ └── toolsets.js
│ │ │ ├── tools.html
│ │ │ └── toolsets.html
│ │ ├── web_test.go
│ │ └── web.go
│ ├── sources
│ │ ├── alloydbadmin
│ │ │ ├── alloydbadmin_test.go
│ │ │ └── alloydbadmin.go
│ │ ├── alloydbpg
│ │ │ ├── alloydb_pg_test.go
│ │ │ └── alloydb_pg.go
│ │ ├── bigquery
│ │ │ ├── bigquery_test.go
│ │ │ ├── bigquery.go
│ │ │ └── cache.go
│ │ ├── bigtable
│ │ │ ├── bigtable_test.go
│ │ │ └── bigtable.go
│ │ ├── cassandra
│ │ │ ├── cassandra_test.go
│ │ │ └── cassandra.go
│ │ ├── clickhouse
│ │ │ ├── clickhouse_test.go
│ │ │ └── clickhouse.go
│ │ ├── cloudhealthcare
│ │ │ ├── cloud_healthcare_test.go
│ │ │ └── cloud_healthcare.go
│ │ ├── cloudmonitoring
│ │ │ ├── cloud_monitoring_test.go
│ │ │ └── cloud_monitoring.go
│ │ ├── cloudsqladmin
│ │ │ ├── cloud_sql_admin_test.go
│ │ │ └── cloud_sql_admin.go
│ │ ├── cloudsqlmssql
│ │ │ ├── cloud_sql_mssql_test.go
│ │ │ └── cloud_sql_mssql.go
│ │ ├── cloudsqlmysql
│ │ │ ├── cloud_sql_mysql_test.go
│ │ │ └── cloud_sql_mysql.go
│ │ ├── cloudsqlpg
│ │ │ ├── cloud_sql_pg_test.go
│ │ │ └── cloud_sql_pg.go
│ │ ├── couchbase
│ │ │ ├── couchbase_test.go
│ │ │ └── couchbase.go
│ │ ├── dataplex
│ │ │ ├── dataplex_test.go
│ │ │ └── dataplex.go
│ │ ├── dgraph
│ │ │ ├── dgraph_test.go
│ │ │ └── dgraph.go
│ │ ├── dialect.go
│ │ ├── elasticsearch
│ │ │ ├── elasticsearch_test.go
│ │ │ └── elasticsearch.go
│ │ ├── firebird
│ │ │ ├── firebird_test.go
│ │ │ └── firebird.go
│ │ ├── firestore
│ │ │ ├── firestore_test.go
│ │ │ └── firestore.go
│ │ ├── http
│ │ │ ├── http_test.go
│ │ │ └── http.go
│ │ ├── ip_type.go
│ │ ├── looker
│ │ │ ├── looker_test.go
│ │ │ └── looker.go
│ │ ├── mindsdb
│ │ │ ├── mindsdb_test.go
│ │ │ └── mindsdb.go
│ │ ├── mongodb
│ │ │ ├── mongodb_test.go
│ │ │ └── mongodb.go
│ │ ├── mssql
│ │ │ ├── mssql_test.go
│ │ │ └── mssql.go
│ │ ├── mysql
│ │ │ ├── mysql_test.go
│ │ │ └── mysql.go
│ │ ├── neo4j
│ │ │ ├── neo4j_test.go
│ │ │ └── neo4j.go
│ │ ├── oceanbase
│ │ │ ├── oceanbase_test.go
│ │ │ └── oceanbase.go
│ │ ├── oracle
│ │ │ └── oracle.go
│ │ ├── postgres
│ │ │ ├── postgres_test.go
│ │ │ └── postgres.go
│ │ ├── redis
│ │ │ ├── redis_test.go
│ │ │ └── redis.go
│ │ ├── serverlessspark
│ │ │ ├── serverlessspark_test.go
│ │ │ └── serverlessspark.go
│ │ ├── singlestore
│ │ │ ├── singlestore_test.go
│ │ │ └── singlestore.go
│ │ ├── sources.go
│ │ ├── spanner
│ │ │ ├── spanner_test.go
│ │ │ └── spanner.go
│ │ ├── sqlite
│ │ │ ├── sqlite_test.go
│ │ │ └── sqlite.go
│ │ ├── tidb
│ │ │ ├── tidb_test.go
│ │ │ └── tidb.go
│ │ ├── trino
│ │ │ ├── trino_test.go
│ │ │ └── trino.go
│ │ ├── util.go
│ │ ├── valkey
│ │ │ ├── valkey_test.go
│ │ │ └── valkey.go
│ │ └── yugabytedb
│ │ ├── yugabytedb_test.go
│ │ └── yugabytedb.go
│ ├── telemetry
│ │ ├── instrumentation.go
│ │ └── telemetry.go
│ ├── testutils
│ │ └── testutils.go
│ ├── tools
│ │ ├── alloydb
│ │ │ ├── alloydbcreatecluster
│ │ │ │ ├── alloydbcreatecluster_test.go
│ │ │ │ └── alloydbcreatecluster.go
│ │ │ ├── alloydbcreateinstance
│ │ │ │ ├── alloydbcreateinstance_test.go
│ │ │ │ └── alloydbcreateinstance.go
│ │ │ ├── alloydbcreateuser
│ │ │ │ ├── alloydbcreateuser_test.go
│ │ │ │ └── alloydbcreateuser.go
│ │ │ ├── alloydbgetcluster
│ │ │ │ ├── alloydbgetcluster_test.go
│ │ │ │ └── alloydbgetcluster.go
│ │ │ ├── alloydbgetinstance
│ │ │ │ ├── alloydbgetinstance_test.go
│ │ │ │ └── alloydbgetinstance.go
│ │ │ ├── alloydbgetuser
│ │ │ │ ├── alloydbgetuser_test.go
│ │ │ │ └── alloydbgetuser.go
│ │ │ ├── alloydblistclusters
│ │ │ │ ├── alloydblistclusters_test.go
│ │ │ │ └── alloydblistclusters.go
│ │ │ ├── alloydblistinstances
│ │ │ │ ├── alloydblistinstances_test.go
│ │ │ │ └── alloydblistinstances.go
│ │ │ ├── alloydblistusers
│ │ │ │ ├── alloydblistusers_test.go
│ │ │ │ └── alloydblistusers.go
│ │ │ └── alloydbwaitforoperation
│ │ │ ├── alloydbwaitforoperation_test.go
│ │ │ └── alloydbwaitforoperation.go
│ │ ├── alloydbainl
│ │ │ ├── alloydbainl_test.go
│ │ │ └── alloydbainl.go
│ │ ├── bigquery
│ │ │ ├── bigqueryanalyzecontribution
│ │ │ │ ├── bigqueryanalyzecontribution_test.go
│ │ │ │ └── bigqueryanalyzecontribution.go
│ │ │ ├── bigquerycommon
│ │ │ │ ├── table_name_parser_test.go
│ │ │ │ ├── table_name_parser.go
│ │ │ │ └── util.go
│ │ │ ├── bigqueryconversationalanalytics
│ │ │ │ ├── bigqueryconversationalanalytics_test.go
│ │ │ │ └── bigqueryconversationalanalytics.go
│ │ │ ├── bigqueryexecutesql
│ │ │ │ ├── bigqueryexecutesql_test.go
│ │ │ │ └── bigqueryexecutesql.go
│ │ │ ├── bigqueryforecast
│ │ │ │ ├── bigqueryforecast_test.go
│ │ │ │ └── bigqueryforecast.go
│ │ │ ├── bigquerygetdatasetinfo
│ │ │ │ ├── bigquerygetdatasetinfo_test.go
│ │ │ │ └── bigquerygetdatasetinfo.go
│ │ │ ├── bigquerygettableinfo
│ │ │ │ ├── bigquerygettableinfo_test.go
│ │ │ │ └── bigquerygettableinfo.go
│ │ │ ├── bigquerylistdatasetids
│ │ │ │ ├── bigquerylistdatasetids_test.go
│ │ │ │ └── bigquerylistdatasetids.go
│ │ │ ├── bigquerylisttableids
│ │ │ │ ├── bigquerylisttableids_test.go
│ │ │ │ └── bigquerylisttableids.go
│ │ │ ├── bigquerysearchcatalog
│ │ │ │ ├── bigquerysearchcatalog_test.go
│ │ │ │ └── bigquerysearchcatalog.go
│ │ │ └── bigquerysql
│ │ │ ├── bigquerysql_test.go
│ │ │ └── bigquerysql.go
│ │ ├── bigtable
│ │ │ ├── bigtable_test.go
│ │ │ └── bigtable.go
│ │ ├── cassandra
│ │ │ └── cassandracql
│ │ │ ├── cassandracql_test.go
│ │ │ └── cassandracql.go
│ │ ├── clickhouse
│ │ │ ├── clickhouseexecutesql
│ │ │ │ ├── clickhouseexecutesql_test.go
│ │ │ │ └── clickhouseexecutesql.go
│ │ │ ├── clickhouselistdatabases
│ │ │ │ ├── clickhouselistdatabases_test.go
│ │ │ │ └── clickhouselistdatabases.go
│ │ │ ├── clickhouselisttables
│ │ │ │ ├── clickhouselisttables_test.go
│ │ │ │ └── clickhouselisttables.go
│ │ │ └── clickhousesql
│ │ │ ├── clickhousesql_test.go
│ │ │ └── clickhousesql.go
│ │ ├── cloudhealthcare
│ │ │ ├── cloudhealthcarefhirfetchpage
│ │ │ │ ├── cloudhealthcarefhirfetchpage_test.go
│ │ │ │ └── cloudhealthcarefhirfetchpage.go
│ │ │ ├── cloudhealthcarefhirpatienteverything
│ │ │ │ ├── cloudhealthcarefhirpatienteverything_test.go
│ │ │ │ └── cloudhealthcarefhirpatienteverything.go
│ │ │ ├── cloudhealthcarefhirpatientsearch
│ │ │ │ ├── cloudhealthcarefhirpatientsearch_test.go
│ │ │ │ └── cloudhealthcarefhirpatientsearch.go
│ │ │ ├── cloudhealthcaregetdataset
│ │ │ │ ├── cloudhealthcaregetdataset_test.go
│ │ │ │ └── cloudhealthcaregetdataset.go
│ │ │ ├── cloudhealthcaregetdicomstore
│ │ │ │ ├── cloudhealthcaregetdicomstore_test.go
│ │ │ │ └── cloudhealthcaregetdicomstore.go
│ │ │ ├── cloudhealthcaregetdicomstoremetrics
│ │ │ │ ├── cloudhealthcaregetdicomstoremetrics_test.go
│ │ │ │ └── cloudhealthcaregetdicomstoremetrics.go
│ │ │ ├── cloudhealthcaregetfhirresource
│ │ │ │ ├── cloudhealthcaregetfhirresource_test.go
│ │ │ │ └── cloudhealthcaregetfhirresource.go
│ │ │ ├── cloudhealthcaregetfhirstore
│ │ │ │ ├── cloudhealthcaregetfhirstore_test.go
│ │ │ │ └── cloudhealthcaregetfhirstore.go
│ │ │ ├── cloudhealthcaregetfhirstoremetrics
│ │ │ │ ├── cloudhealthcaregetfhirstoremetrics_test.go
│ │ │ │ └── cloudhealthcaregetfhirstoremetrics.go
│ │ │ ├── cloudhealthcarelistdicomstores
│ │ │ │ ├── cloudhealthcarelistdicomstores_test.go
│ │ │ │ └── cloudhealthcarelistdicomstores.go
│ │ │ ├── cloudhealthcarelistfhirstores
│ │ │ │ ├── cloudhealthcarelistfhirstores_test.go
│ │ │ │ └── cloudhealthcarelistfhirstores.go
│ │ │ ├── cloudhealthcareretrieverendereddicominstance
│ │ │ │ ├── cloudhealthcareretrieverendereddicominstance_test.go
│ │ │ │ └── cloudhealthcareretrieverendereddicominstance.go
│ │ │ ├── cloudhealthcaresearchdicominstances
│ │ │ │ ├── cloudhealthcaresearchdicominstances_test.go
│ │ │ │ └── cloudhealthcaresearchdicominstances.go
│ │ │ ├── cloudhealthcaresearchdicomseries
│ │ │ │ ├── cloudhealthcaresearchdicomseries_test.go
│ │ │ │ └── cloudhealthcaresearchdicomseries.go
│ │ │ ├── cloudhealthcaresearchdicomstudies
│ │ │ │ ├── cloudhealthcaresearchdicomstudies_test.go
│ │ │ │ └── cloudhealthcaresearchdicomstudies.go
│ │ │ └── common
│ │ │ └── util.go
│ │ ├── cloudmonitoring
│ │ │ ├── cloudmonitoring_test.go
│ │ │ └── cloudmonitoring.go
│ │ ├── cloudsql
│ │ │ ├── cloudsqlcreatedatabase
│ │ │ │ ├── cloudsqlcreatedatabase_test.go
│ │ │ │ └── cloudsqlcreatedatabase.go
│ │ │ ├── cloudsqlcreateusers
│ │ │ │ ├── cloudsqlcreateusers_test.go
│ │ │ │ └── cloudsqlcreateusers.go
│ │ │ ├── cloudsqlgetinstances
│ │ │ │ ├── cloudsqlgetinstances_test.go
│ │ │ │ └── cloudsqlgetinstances.go
│ │ │ ├── cloudsqllistdatabases
│ │ │ │ ├── cloudsqllistdatabases_test.go
│ │ │ │ └── cloudsqllistdatabases.go
│ │ │ ├── cloudsqllistinstances
│ │ │ │ ├── cloudsqllistinstances_test.go
│ │ │ │ └── cloudsqllistinstances.go
│ │ │ └── cloudsqlwaitforoperation
│ │ │ ├── cloudsqlwaitforoperation_test.go
│ │ │ └── cloudsqlwaitforoperation.go
│ │ ├── cloudsqlmssql
│ │ │ └── cloudsqlmssqlcreateinstance
│ │ │ ├── cloudsqlmssqlcreateinstance_test.go
│ │ │ └── cloudsqlmssqlcreateinstance.go
│ │ ├── cloudsqlmysql
│ │ │ └── cloudsqlmysqlcreateinstance
│ │ │ ├── cloudsqlmysqlcreateinstance_test.go
│ │ │ └── cloudsqlmysqlcreateinstance.go
│ │ ├── cloudsqlpg
│ │ │ └── cloudsqlpgcreateinstances
│ │ │ ├── cloudsqlpgcreateinstances_test.go
│ │ │ └── cloudsqlpgcreateinstances.go
│ │ ├── common_test.go
│ │ ├── common.go
│ │ ├── couchbase
│ │ │ ├── couchbase_test.go
│ │ │ └── couchbase.go
│ │ ├── dataform
│ │ │ └── dataformcompilelocal
│ │ │ ├── dataformcompilelocal_test.go
│ │ │ └── dataformcompilelocal.go
│ │ ├── dataplex
│ │ │ ├── dataplexlookupentry
│ │ │ │ ├── dataplexlookupentry_test.go
│ │ │ │ └── dataplexlookupentry.go
│ │ │ ├── dataplexsearchaspecttypes
│ │ │ │ ├── dataplexsearchaspecttypes_test.go
│ │ │ │ └── dataplexsearchaspecttypes.go
│ │ │ └── dataplexsearchentries
│ │ │ ├── dataplexsearchentries_test.go
│ │ │ └── dataplexsearchentries.go
│ │ ├── dgraph
│ │ │ ├── dgraph_test.go
│ │ │ └── dgraph.go
│ │ ├── elasticsearch
│ │ │ └── elasticsearchesql
│ │ │ ├── elasticsearchesql_test.go
│ │ │ └── elasticsearchesql.go
│ │ ├── firebird
│ │ │ ├── firebirdexecutesql
│ │ │ │ ├── firebirdexecutesql_test.go
│ │ │ │ └── firebirdexecutesql.go
│ │ │ └── firebirdsql
│ │ │ ├── firebirdsql_test.go
│ │ │ └── firebirdsql.go
│ │ ├── firestore
│ │ │ ├── firestoreadddocuments
│ │ │ │ ├── firestoreadddocuments_test.go
│ │ │ │ └── firestoreadddocuments.go
│ │ │ ├── firestoredeletedocuments
│ │ │ │ ├── firestoredeletedocuments_test.go
│ │ │ │ └── firestoredeletedocuments.go
│ │ │ ├── firestoregetdocuments
│ │ │ │ ├── firestoregetdocuments_test.go
│ │ │ │ └── firestoregetdocuments.go
│ │ │ ├── firestoregetrules
│ │ │ │ ├── firestoregetrules_test.go
│ │ │ │ └── firestoregetrules.go
│ │ │ ├── firestorelistcollections
│ │ │ │ ├── firestorelistcollections_test.go
│ │ │ │ └── firestorelistcollections.go
│ │ │ ├── firestorequery
│ │ │ │ ├── firestorequery_test.go
│ │ │ │ └── firestorequery.go
│ │ │ ├── firestorequerycollection
│ │ │ │ ├── firestorequerycollection_test.go
│ │ │ │ └── firestorequerycollection.go
│ │ │ ├── firestoreupdatedocument
│ │ │ │ ├── firestoreupdatedocument_test.go
│ │ │ │ └── firestoreupdatedocument.go
│ │ │ ├── firestorevalidaterules
│ │ │ │ ├── firestorevalidaterules_test.go
│ │ │ │ └── firestorevalidaterules.go
│ │ │ └── util
│ │ │ ├── converter_test.go
│ │ │ ├── converter.go
│ │ │ ├── validator_test.go
│ │ │ └── validator.go
│ │ ├── http
│ │ │ ├── http_test.go
│ │ │ └── http.go
│ │ ├── http_method.go
│ │ ├── looker
│ │ │ ├── lookeradddashboardelement
│ │ │ │ ├── lookeradddashboardelement_test.go
│ │ │ │ └── lookeradddashboardelement.go
│ │ │ ├── lookercommon
│ │ │ │ ├── lookercommon_test.go
│ │ │ │ └── lookercommon.go
│ │ │ ├── lookerconversationalanalytics
│ │ │ │ ├── lookerconversationalanalytics_test.go
│ │ │ │ └── lookerconversationalanalytics.go
│ │ │ ├── lookercreateprojectfile
│ │ │ │ ├── lookercreateprojectfile_test.go
│ │ │ │ └── lookercreateprojectfile.go
│ │ │ ├── lookerdeleteprojectfile
│ │ │ │ ├── lookerdeleteprojectfile_test.go
│ │ │ │ └── lookerdeleteprojectfile.go
│ │ │ ├── lookerdevmode
│ │ │ │ ├── lookerdevmode_test.go
│ │ │ │ └── lookerdevmode.go
│ │ │ ├── lookergetconnectiondatabases
│ │ │ │ ├── lookergetconnectiondatabases_test.go
│ │ │ │ └── lookergetconnectiondatabases.go
│ │ │ ├── lookergetconnections
│ │ │ │ ├── lookergetconnections_test.go
│ │ │ │ └── lookergetconnections.go
│ │ │ ├── lookergetconnectionschemas
│ │ │ │ ├── lookergetconnectionschemas_test.go
│ │ │ │ └── lookergetconnectionschemas.go
│ │ │ ├── lookergetconnectiontablecolumns
│ │ │ │ ├── lookergetconnectiontablecolumns_test.go
│ │ │ │ └── lookergetconnectiontablecolumns.go
│ │ │ ├── lookergetconnectiontables
│ │ │ │ ├── lookergetconnectiontables_test.go
│ │ │ │ └── lookergetconnectiontables.go
│ │ │ ├── lookergetdashboards
│ │ │ │ ├── lookergetdashboards_test.go
│ │ │ │ └── lookergetdashboards.go
│ │ │ ├── lookergetdimensions
│ │ │ │ ├── lookergetdimensions_test.go
│ │ │ │ └── lookergetdimensions.go
│ │ │ ├── lookergetexplores
│ │ │ │ ├── lookergetexplores_test.go
│ │ │ │ └── lookergetexplores.go
│ │ │ ├── lookergetfilters
│ │ │ │ ├── lookergetfilters_test.go
│ │ │ │ └── lookergetfilters.go
│ │ │ ├── lookergetlooks
│ │ │ │ ├── lookergetlooks_test.go
│ │ │ │ └── lookergetlooks.go
│ │ │ ├── lookergetmeasures
│ │ │ │ ├── lookergetmeasures_test.go
│ │ │ │ └── lookergetmeasures.go
│ │ │ ├── lookergetmodels
│ │ │ │ ├── lookergetmodels_test.go
│ │ │ │ └── lookergetmodels.go
│ │ │ ├── lookergetparameters
│ │ │ │ ├── lookergetparameters_test.go
│ │ │ │ └── lookergetparameters.go
│ │ │ ├── lookergetprojectfile
│ │ │ │ ├── lookergetprojectfile_test.go
│ │ │ │ └── lookergetprojectfile.go
│ │ │ ├── lookergetprojectfiles
│ │ │ │ ├── lookergetprojectfiles_test.go
│ │ │ │ └── lookergetprojectfiles.go
│ │ │ ├── lookergetprojects
│ │ │ │ ├── lookergetprojects_test.go
│ │ │ │ └── lookergetprojects.go
│ │ │ ├── lookerhealthanalyze
│ │ │ │ ├── lookerhealthanalyze_test.go
│ │ │ │ └── lookerhealthanalyze.go
│ │ │ ├── lookerhealthpulse
│ │ │ │ ├── lookerhealthpulse_test.go
│ │ │ │ └── lookerhealthpulse.go
│ │ │ ├── lookerhealthvacuum
│ │ │ │ ├── lookerhealthvacuum_test.go
│ │ │ │ └── lookerhealthvacuum.go
│ │ │ ├── lookermakedashboard
│ │ │ │ ├── lookermakedashboard_test.go
│ │ │ │ └── lookermakedashboard.go
│ │ │ ├── lookermakelook
│ │ │ │ ├── lookermakelook_test.go
│ │ │ │ └── lookermakelook.go
│ │ │ ├── lookerquery
│ │ │ │ ├── lookerquery_test.go
│ │ │ │ └── lookerquery.go
│ │ │ ├── lookerquerysql
│ │ │ │ ├── lookerquerysql_test.go
│ │ │ │ └── lookerquerysql.go
│ │ │ ├── lookerqueryurl
│ │ │ │ ├── lookerqueryurl_test.go
│ │ │ │ └── lookerqueryurl.go
│ │ │ ├── lookerrundashboard
│ │ │ │ ├── lookerrundashboard_test.go
│ │ │ │ └── lookerrundashboard.go
│ │ │ ├── lookerrunlook
│ │ │ │ ├── lookerrunlook_test.go
│ │ │ │ └── lookerrunlook.go
│ │ │ └── lookerupdateprojectfile
│ │ │ ├── lookerupdateprojectfile_test.go
│ │ │ └── lookerupdateprojectfile.go
│ │ ├── mindsdb
│ │ │ ├── mindsdbexecutesql
│ │ │ │ ├── mindsdbexecutesql_test.go
│ │ │ │ └── mindsdbexecutesql.go
│ │ │ └── mindsdbsql
│ │ │ ├── mindsdbsql_test.go
│ │ │ └── mindsdbsql.go
│ │ ├── mongodb
│ │ │ ├── mongodbaggregate
│ │ │ │ ├── mongodbaggregate_test.go
│ │ │ │ └── mongodbaggregate.go
│ │ │ ├── mongodbdeletemany
│ │ │ │ ├── mongodbdeletemany_test.go
│ │ │ │ └── mongodbdeletemany.go
│ │ │ ├── mongodbdeleteone
│ │ │ │ ├── mongodbdeleteone_test.go
│ │ │ │ └── mongodbdeleteone.go
│ │ │ ├── mongodbfind
│ │ │ │ ├── mongodbfind_test.go
│ │ │ │ └── mongodbfind.go
│ │ │ ├── mongodbfindone
│ │ │ │ ├── mongodbfindone_test.go
│ │ │ │ └── mongodbfindone.go
│ │ │ ├── mongodbinsertmany
│ │ │ │ ├── mongodbinsertmany_test.go
│ │ │ │ └── mongodbinsertmany.go
│ │ │ ├── mongodbinsertone
│ │ │ │ ├── mongodbinsertone_test.go
│ │ │ │ └── mongodbinsertone.go
│ │ │ ├── mongodbupdatemany
│ │ │ │ ├── mongodbupdatemany_test.go
│ │ │ │ └── mongodbupdatemany.go
│ │ │ └── mongodbupdateone
│ │ │ ├── mongodbupdateone_test.go
│ │ │ └── mongodbupdateone.go
│ │ ├── mssql
│ │ │ ├── mssqlexecutesql
│ │ │ │ ├── mssqlexecutesql_test.go
│ │ │ │ └── mssqlexecutesql.go
│ │ │ ├── mssqllisttables
│ │ │ │ ├── mssqllisttables_test.go
│ │ │ │ └── mssqllisttables.go
│ │ │ └── mssqlsql
│ │ │ ├── mssqlsql_test.go
│ │ │ └── mssqlsql.go
│ │ ├── mysql
│ │ │ ├── mysqlcommon
│ │ │ │ └── mysqlcommon.go
│ │ │ ├── mysqlexecutesql
│ │ │ │ ├── mysqlexecutesql_test.go
│ │ │ │ └── mysqlexecutesql.go
│ │ │ ├── mysqllistactivequeries
│ │ │ │ ├── mysqllistactivequeries_test.go
│ │ │ │ └── mysqllistactivequeries.go
│ │ │ ├── mysqllisttablefragmentation
│ │ │ │ ├── mysqllisttablefragmentation_test.go
│ │ │ │ └── mysqllisttablefragmentation.go
│ │ │ ├── mysqllisttables
│ │ │ │ ├── mysqllisttables_test.go
│ │ │ │ └── mysqllisttables.go
│ │ │ ├── mysqllisttablesmissinguniqueindexes
│ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go
│ │ │ │ └── mysqllisttablesmissinguniqueindexes.go
│ │ │ └── mysqlsql
│ │ │ ├── mysqlsql_test.go
│ │ │ └── mysqlsql.go
│ │ ├── neo4j
│ │ │ ├── neo4jcypher
│ │ │ │ ├── neo4jcypher_test.go
│ │ │ │ └── neo4jcypher.go
│ │ │ ├── neo4jexecutecypher
│ │ │ │ ├── classifier
│ │ │ │ │ ├── classifier_test.go
│ │ │ │ │ └── classifier.go
│ │ │ │ ├── neo4jexecutecypher_test.go
│ │ │ │ └── neo4jexecutecypher.go
│ │ │ └── neo4jschema
│ │ │ ├── cache
│ │ │ │ ├── cache_test.go
│ │ │ │ └── cache.go
│ │ │ ├── helpers
│ │ │ │ ├── helpers_test.go
│ │ │ │ └── helpers.go
│ │ │ ├── neo4jschema_test.go
│ │ │ ├── neo4jschema.go
│ │ │ └── types
│ │ │ └── types.go
│ │ ├── oceanbase
│ │ │ ├── oceanbaseexecutesql
│ │ │ │ ├── oceanbaseexecutesql_test.go
│ │ │ │ └── oceanbaseexecutesql.go
│ │ │ └── oceanbasesql
│ │ │ ├── oceanbasesql_test.go
│ │ │ └── oceanbasesql.go
│ │ ├── oracle
│ │ │ ├── oracleexecutesql
│ │ │ │ └── oracleexecutesql.go
│ │ │ └── oraclesql
│ │ │ └── oraclesql.go
│ │ ├── parameters_test.go
│ │ ├── parameters.go
│ │ ├── postgres
│ │ │ ├── postgresexecutesql
│ │ │ │ ├── postgresexecutesql_test.go
│ │ │ │ └── postgresexecutesql.go
│ │ │ ├── postgreslistactivequeries
│ │ │ │ ├── postgreslistactivequeries_test.go
│ │ │ │ └── postgreslistactivequeries.go
│ │ │ ├── postgreslistavailableextensions
│ │ │ │ ├── postgreslistavailableextensions_test.go
│ │ │ │ └── postgreslistavailableextensions.go
│ │ │ ├── postgreslistinstalledextensions
│ │ │ │ ├── postgreslistinstalledextensions_test.go
│ │ │ │ └── postgreslistinstalledextensions.go
│ │ │ ├── postgreslistschemas
│ │ │ │ ├── postgreslistschemas_test.go
│ │ │ │ └── postgreslistschemas.go
│ │ │ ├── postgreslisttables
│ │ │ │ ├── postgreslisttables_test.go
│ │ │ │ └── postgreslisttables.go
│ │ │ ├── postgreslistviews
│ │ │ │ ├── postgreslistviews_test.go
│ │ │ │ └── postgreslistviews.go
│ │ │ └── postgressql
│ │ │ ├── postgressql_test.go
│ │ │ └── postgressql.go
│ │ ├── redis
│ │ │ ├── redis_test.go
│ │ │ └── redis.go
│ │ ├── serverlessspark
│ │ │ ├── serverlesssparkcancelbatch
│ │ │ │ ├── serverlesssparkcancelbatch_test.go
│ │ │ │ └── serverlesssparkcancelbatch.go
│ │ │ ├── serverlesssparkgetbatch
│ │ │ │ ├── serverlesssparkgetbatch_test.go
│ │ │ │ └── serverlesssparkgetbatch.go
│ │ │ └── serverlesssparklistbatches
│ │ │ ├── serverlesssparklistbatches_test.go
│ │ │ └── serverlesssparklistbatches.go
│ │ ├── singlestore
│ │ │ ├── singlestoreexecutesql
│ │ │ │ ├── singlestoreexecutesql_test.go
│ │ │ │ └── singlestoreexecutesql.go
│ │ │ └── singlestoresql
│ │ │ ├── singlestoresql_test.go
│ │ │ └── singlestoresql.go
│ │ ├── spanner
│ │ │ ├── spannerexecutesql
│ │ │ │ ├── spannerexecutesql_test.go
│ │ │ │ └── spannerexecutesql.go
│ │ │ ├── spannerlisttables
│ │ │ │ ├── spannerlisttables_test.go
│ │ │ │ └── spannerlisttables.go
│ │ │ └── spannersql
│ │ │ ├── spanner_test.go
│ │ │ └── spannersql.go
│ │ ├── sqlite
│ │ │ ├── sqliteexecutesql
│ │ │ │ ├── sqliteexecutesql_test.go
│ │ │ │ └── sqliteexecutesql.go
│ │ │ └── sqlitesql
│ │ │ ├── sqlitesql_test.go
│ │ │ └── sqlitesql.go
│ │ ├── tidb
│ │ │ ├── tidbexecutesql
│ │ │ │ ├── tidbexecutesql_test.go
│ │ │ │ └── tidbexecutesql.go
│ │ │ └── tidbsql
│ │ │ ├── tidbsql_test.go
│ │ │ └── tidbsql.go
│ │ ├── tools_test.go
│ │ ├── tools.go
│ │ ├── toolsets.go
│ │ ├── trino
│ │ │ ├── trinoexecutesql
│ │ │ │ ├── trinoexecutesql_test.go
│ │ │ │ └── trinoexecutesql.go
│ │ │ └── trinosql
│ │ │ ├── trinosql_test.go
│ │ │ └── trinosql.go
│ │ ├── utility
│ │ │ └── wait
│ │ │ ├── wait_test.go
│ │ │ └── wait.go
│ │ ├── valkey
│ │ │ ├── valkey_test.go
│ │ │ └── valkey.go
│ │ └── yugabytedbsql
│ │ ├── yugabytedbsql_test.go
│ │ └── yugabytedbsql.go
│ └── util
│ ├── orderedmap
│ │ ├── orderedmap_test.go
│ │ └── orderedmap.go
│ └── util.go
├── LICENSE
├── logo.png
├── main.go
├── MCP-TOOLBOX-EXTENSION.md
├── README.md
└── tests
├── alloydb
│ ├── alloydb_integration_test.go
│ └── alloydb_wait_for_operation_test.go
├── alloydbainl
│ └── alloydb_ai_nl_integration_test.go
├── alloydbpg
│ └── alloydb_pg_integration_test.go
├── auth.go
├── bigquery
│ └── bigquery_integration_test.go
├── bigtable
│ └── bigtable_integration_test.go
├── cassandra
│ └── cassandra_integration_test.go
├── clickhouse
│ └── clickhouse_integration_test.go
├── cloudhealthcare
│ └── cloud_healthcare_integration_test.go
├── cloudmonitoring
│ └── cloud_monitoring_integration_test.go
├── cloudsql
│ ├── cloud_sql_create_database_test.go
│ ├── cloud_sql_create_users_test.go
│ ├── cloud_sql_get_instances_test.go
│ ├── cloud_sql_list_databases_test.go
│ ├── cloudsql_list_instances_test.go
│ └── cloudsql_wait_for_operation_test.go
├── cloudsqlmssql
│ ├── cloud_sql_mssql_create_instance_integration_test.go
│ └── cloud_sql_mssql_integration_test.go
├── cloudsqlmysql
│ ├── cloud_sql_mysql_create_instance_integration_test.go
│ └── cloud_sql_mysql_integration_test.go
├── cloudsqlpg
│ ├── cloud_sql_pg_create_instances_test.go
│ └── cloud_sql_pg_integration_test.go
├── common.go
├── couchbase
│ └── couchbase_integration_test.go
├── dataform
│ └── dataform_integration_test.go
├── dataplex
│ └── dataplex_integration_test.go
├── dgraph
│ └── dgraph_integration_test.go
├── elasticsearch
│ └── elasticsearch_integration_test.go
├── firebird
│ └── firebird_integration_test.go
├── firestore
│ └── firestore_integration_test.go
├── http
│ └── http_integration_test.go
├── looker
│ └── looker_integration_test.go
├── mindsdb
│ └── mindsdb_integration_test.go
├── mongodb
│ └── mongodb_integration_test.go
├── mssql
│ └── mssql_integration_test.go
├── mysql
│ └── mysql_integration_test.go
├── neo4j
│ └── neo4j_integration_test.go
├── oceanbase
│ └── oceanbase_integration_test.go
├── option.go
├── oracle
│ └── oracle_integration_test.go
├── postgres
│ └── postgres_integration_test.go
├── redis
│ └── redis_test.go
├── server.go
├── serverlessspark
│ └── serverless_spark_integration_test.go
├── singlestore
│ └── singlestore_integration_test.go
├── source.go
├── spanner
│ └── spanner_integration_test.go
├── sqlite
│ └── sqlite_integration_test.go
├── tidb
│ └── tidb_integration_test.go
├── tool.go
├── trino
│ └── trino_integration_test.go
├── utility
│ └── wait_integration_test.go
├── valkey
│ └── valkey_test.go
└── yugabytedb
└── yugabytedb_integration_test.go
```
# Files
--------------------------------------------------------------------------------
/tests/looker/looker_integration_test.go:
--------------------------------------------------------------------------------
```go
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package looker
16 |
17 | import (
18 | "bytes"
19 | "context"
20 | "encoding/json"
21 | "fmt"
22 | "net/http"
23 | "os"
24 | "regexp"
25 | "strings"
26 | "testing"
27 | "time"
28 |
29 | "github.com/googleapis/genai-toolbox/internal/log"
30 | "github.com/googleapis/genai-toolbox/internal/testutils"
31 | "github.com/googleapis/genai-toolbox/internal/util"
32 | "github.com/googleapis/genai-toolbox/tests"
33 | )
34 |
35 | var (
36 | LookerSourceKind = "looker"
37 | LookerBaseUrl = os.Getenv("LOOKER_BASE_URL")
38 | LookerVerifySsl = os.Getenv("LOOKER_VERIFY_SSL")
39 | LookerClientId = os.Getenv("LOOKER_CLIENT_ID")
40 | LookerClientSecret = os.Getenv("LOOKER_CLIENT_SECRET")
41 | LookerProject = os.Getenv("LOOKER_PROJECT")
42 | LookerLocation = os.Getenv("LOOKER_LOCATION")
43 | )
44 |
45 | func getLookerVars(t *testing.T) map[string]any {
46 | switch "" {
47 | case LookerBaseUrl:
48 | t.Fatal("'LOOKER_BASE_URL' not set")
49 | case LookerVerifySsl:
50 | t.Fatal("'LOOKER_VERIFY_SSL' not set")
51 | case LookerClientId:
52 | t.Fatal("'LOOKER_CLIENT_ID' not set")
53 | case LookerClientSecret:
54 | t.Fatal("'LOOKER_CLIENT_SECRET' not set")
55 | case LookerProject:
56 | t.Fatal("'LOOKER_PROJECT' not set")
57 | case LookerLocation:
58 | t.Fatal("'LOOKER_LOCATION' not set")
59 | }
60 |
61 | return map[string]any{
62 | "kind": LookerSourceKind,
63 | "base_url": LookerBaseUrl,
64 | "verify_ssl": (LookerVerifySsl == "true"),
65 | "client_id": LookerClientId,
66 | "client_secret": LookerClientSecret,
67 | "project": LookerProject,
68 | "location": LookerLocation,
69 | }
70 | }
71 |
72 | func TestLooker(t *testing.T) {
73 | sourceConfig := getLookerVars(t)
74 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
75 | defer cancel()
76 |
77 | testLogger, err := log.NewStdLogger(os.Stdout, os.Stderr, "info")
78 | if err != nil {
79 | t.Fatalf("unexpected error: %s", err)
80 | }
81 | ctx = util.WithLogger(ctx, testLogger)
82 |
83 | var args []string
84 |
85 | // Write config into a file and pass it to command
86 |
87 | toolsFile := map[string]any{
88 | "sources": map[string]any{
89 | "my-instance": sourceConfig,
90 | },
91 | "tools": map[string]any{
92 | "get_models": map[string]any{
93 | "kind": "looker-get-models",
94 | "source": "my-instance",
95 | "description": "Simple tool to test end to end functionality.",
96 | },
97 | "get_explores": map[string]any{
98 | "kind": "looker-get-explores",
99 | "source": "my-instance",
100 | "description": "Simple tool to test end to end functionality.",
101 | },
102 | "get_dimensions": map[string]any{
103 | "kind": "looker-get-dimensions",
104 | "source": "my-instance",
105 | "description": "Simple tool to test end to end functionality.",
106 | },
107 | "get_measures": map[string]any{
108 | "kind": "looker-get-measures",
109 | "source": "my-instance",
110 | "description": "Simple tool to test end to end functionality.",
111 | },
112 | "get_filters": map[string]any{
113 | "kind": "looker-get-filters",
114 | "source": "my-instance",
115 | "description": "Simple tool to test end to end functionality.",
116 | },
117 | "get_parameters": map[string]any{
118 | "kind": "looker-get-parameters",
119 | "source": "my-instance",
120 | "description": "Simple tool to test end to end functionality.",
121 | },
122 | "query": map[string]any{
123 | "kind": "looker-query",
124 | "source": "my-instance",
125 | "description": "Simple tool to test end to end functionality.",
126 | },
127 | "query_sql": map[string]any{
128 | "kind": "looker-query-sql",
129 | "source": "my-instance",
130 | "description": "Simple tool to test end to end functionality.",
131 | },
132 | "query_url": map[string]any{
133 | "kind": "looker-query-url",
134 | "source": "my-instance",
135 | "description": "Simple tool to test end to end functionality.",
136 | },
137 | "get_looks": map[string]any{
138 | "kind": "looker-get-looks",
139 | "source": "my-instance",
140 | "description": "Simple tool to test end to end functionality.",
141 | },
142 | "get_dashboards": map[string]any{
143 | "kind": "looker-get-dashboards",
144 | "source": "my-instance",
145 | "description": "Simple tool to test end to end functionality.",
146 | },
147 | "conversational_analytics": map[string]any{
148 | "kind": "looker-conversational-analytics",
149 | "source": "my-instance",
150 | "description": "Simple tool to test end to end functionality.",
151 | },
152 | "health_pulse": map[string]any{
153 | "kind": "looker-health-pulse",
154 | "source": "my-instance",
155 | "description": "Checks the health of a Looker instance by running a series of checks on the system.",
156 | },
157 | "health_analyze": map[string]any{
158 | "kind": "looker-health-analyze",
159 | "source": "my-instance",
160 | "description": "Provides analysis of a Looker instance's projects, models, or explores.",
161 | },
162 | "health_vacuum": map[string]any{
163 | "kind": "looker-health-vacuum",
164 | "source": "my-instance",
165 | "description": "Vacuums unused content from a Looker instance.",
166 | },
167 | "dev_mode": map[string]any{
168 | "kind": "looker-dev-mode",
169 | "source": "my-instance",
170 | "description": "Simple tool to test end to end functionality.",
171 | },
172 | "get_projects": map[string]any{
173 | "kind": "looker-get-projects",
174 | "source": "my-instance",
175 | "description": "Simple tool to test end to end functionality.",
176 | },
177 | "get_project_files": map[string]any{
178 | "kind": "looker-get-project-files",
179 | "source": "my-instance",
180 | "description": "Simple tool to test end to end functionality.",
181 | },
182 | "get_project_file": map[string]any{
183 | "kind": "looker-get-project-file",
184 | "source": "my-instance",
185 | "description": "Simple tool to test end to end functionality.",
186 | },
187 | "create_project_file": map[string]any{
188 | "kind": "looker-create-project-file",
189 | "source": "my-instance",
190 | "description": "Simple tool to test end to end functionality.",
191 | },
192 | "update_project_file": map[string]any{
193 | "kind": "looker-update-project-file",
194 | "source": "my-instance",
195 | "description": "Simple tool to test end to end functionality.",
196 | },
197 | "delete_project_file": map[string]any{
198 | "kind": "looker-delete-project-file",
199 | "source": "my-instance",
200 | "description": "Simple tool to test end to end functionality.",
201 | },
202 | "get_connections": map[string]any{
203 | "kind": "looker-get-connections",
204 | "source": "my-instance",
205 | "description": "Simple tool to test end to end functionality.",
206 | },
207 | "get_connection_schemas": map[string]any{
208 | "kind": "looker-get-connection-schemas",
209 | "source": "my-instance",
210 | "description": "Simple tool to test end to end functionality.",
211 | },
212 | "get_connection_databases": map[string]any{
213 | "kind": "looker-get-connection-databases",
214 | "source": "my-instance",
215 | "description": "Simple tool to test end to end functionality.",
216 | },
217 | "get_connection_tables": map[string]any{
218 | "kind": "looker-get-connection-tables",
219 | "source": "my-instance",
220 | "description": "Simple tool to test end to end functionality.",
221 | },
222 | "get_connection_table_columns": map[string]any{
223 | "kind": "looker-get-connection-table-columns",
224 | "source": "my-instance",
225 | "description": "Simple tool to test end to end functionality.",
226 | },
227 | },
228 | }
229 |
230 | cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
231 | if err != nil {
232 | t.Fatalf("command initialization returned an error: %s", err)
233 | }
234 | defer cleanup()
235 |
236 | waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
237 | defer cancel()
238 | out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
239 | if err != nil {
240 | t.Logf("toolbox command logs: \n%s", out)
241 | t.Fatalf("toolbox didn't start successfully: %s", err)
242 | }
243 |
244 | tests.RunToolGetTestByName(t, "get_models",
245 | map[string]any{
246 | "get_models": map[string]any{
247 | "description": "Simple tool to test end to end functionality.",
248 | "authRequired": []any{},
249 | "parameters": []any{},
250 | },
251 | },
252 | )
253 | tests.RunToolGetTestByName(t, "get_explores",
254 | map[string]any{
255 | "get_explores": map[string]any{
256 | "description": "Simple tool to test end to end functionality.",
257 | "authRequired": []any{},
258 | "parameters": []any{
259 | map[string]any{
260 | "authSources": []any{},
261 | "description": "The model containing the explores.",
262 | "name": "model",
263 | "required": true,
264 | "type": "string",
265 | },
266 | },
267 | },
268 | },
269 | )
270 | tests.RunToolGetTestByName(t, "get_dimensions",
271 | map[string]any{
272 | "get_dimensions": map[string]any{
273 | "description": "Simple tool to test end to end functionality.",
274 | "authRequired": []any{},
275 | "parameters": []any{
276 | map[string]any{
277 | "authSources": []any{},
278 | "description": "The model containing the explore.",
279 | "name": "model",
280 | "required": true,
281 | "type": "string",
282 | },
283 | map[string]any{
284 | "authSources": []any{},
285 | "description": "The explore containing the fields.",
286 | "name": "explore",
287 | "required": true,
288 | "type": "string",
289 | },
290 | },
291 | },
292 | },
293 | )
294 | tests.RunToolGetTestByName(t, "get_measures",
295 | map[string]any{
296 | "get_measures": map[string]any{
297 | "description": "Simple tool to test end to end functionality.",
298 | "authRequired": []any{},
299 | "parameters": []any{
300 | map[string]any{
301 | "authSources": []any{},
302 | "description": "The model containing the explore.",
303 | "name": "model",
304 | "required": true,
305 | "type": "string",
306 | },
307 | map[string]any{
308 | "authSources": []any{},
309 | "description": "The explore containing the fields.",
310 | "name": "explore",
311 | "required": true,
312 | "type": "string",
313 | },
314 | },
315 | },
316 | },
317 | )
318 | tests.RunToolGetTestByName(t, "get_parameters",
319 | map[string]any{
320 | "get_parameters": map[string]any{
321 | "description": "Simple tool to test end to end functionality.",
322 | "authRequired": []any{},
323 | "parameters": []any{
324 | map[string]any{
325 | "authSources": []any{},
326 | "description": "The model containing the explore.",
327 | "name": "model",
328 | "required": true,
329 | "type": "string",
330 | },
331 | map[string]any{
332 | "authSources": []any{},
333 | "description": "The explore containing the fields.",
334 | "name": "explore",
335 | "required": true,
336 | "type": "string",
337 | },
338 | },
339 | },
340 | },
341 | )
342 | tests.RunToolGetTestByName(t, "get_filters",
343 | map[string]any{
344 | "get_filters": map[string]any{
345 | "description": "Simple tool to test end to end functionality.",
346 | "authRequired": []any{},
347 | "parameters": []any{
348 | map[string]any{
349 | "authSources": []any{},
350 | "description": "The model containing the explore.",
351 | "name": "model",
352 | "required": true,
353 | "type": "string",
354 | },
355 | map[string]any{
356 | "authSources": []any{},
357 | "description": "The explore containing the fields.",
358 | "name": "explore",
359 | "required": true,
360 | "type": "string",
361 | },
362 | },
363 | },
364 | },
365 | )
366 | tests.RunToolGetTestByName(t, "query",
367 | map[string]any{
368 | "query": map[string]any{
369 | "description": "Simple tool to test end to end functionality.",
370 | "authRequired": []any{},
371 | "parameters": []any{
372 | map[string]any{
373 | "authSources": []any{},
374 | "description": "The model containing the explore.",
375 | "name": "model",
376 | "required": true,
377 | "type": "string",
378 | },
379 | map[string]any{
380 | "authSources": []any{},
381 | "description": "The explore to be queried.",
382 | "name": "explore",
383 | "required": true,
384 | "type": "string",
385 | },
386 | map[string]any{
387 | "authSources": []any{},
388 | "description": "The fields to be retrieved.",
389 | "items": map[string]any{
390 | "authSources": []any{},
391 | "description": "A field to be returned in the query",
392 | "name": "field",
393 | "required": true,
394 | "type": "string",
395 | },
396 | "name": "fields",
397 | "required": true,
398 | "type": "array",
399 | },
400 | map[string]any{
401 | "additionalProperties": true,
402 | "authSources": []any{},
403 | "description": "The filters for the query",
404 | "name": "filters",
405 | "required": false,
406 | "type": "object",
407 | },
408 | map[string]any{
409 | "authSources": []any{},
410 | "description": "The query pivots (must be included in fields as well).",
411 | "items": map[string]any{
412 | "authSources": []any{},
413 | "description": "A field to be used as a pivot in the query",
414 | "name": "pivot_field",
415 | "required": false,
416 | "type": "string",
417 | },
418 | "name": "pivots",
419 | "required": false,
420 | "type": "array",
421 | },
422 | map[string]any{
423 | "authSources": []any{},
424 | "description": "The sorts like \"field.id desc 0\".",
425 | "items": map[string]any{
426 | "authSources": []any{},
427 | "description": "A field to be used as a sort in the query",
428 | "name": "sort_field",
429 | "required": false,
430 | "type": "string",
431 | },
432 | "name": "sorts",
433 | "required": false,
434 | "type": "array",
435 | },
436 | map[string]any{
437 | "authSources": []any{},
438 | "description": "The row limit.",
439 | "name": "limit",
440 | "required": false,
441 | "type": "integer",
442 | },
443 | map[string]any{
444 | "authSources": []any{},
445 | "description": "The query timezone.",
446 | "name": "tz",
447 | "required": false,
448 | "type": "string",
449 | },
450 | },
451 | },
452 | },
453 | )
454 | tests.RunToolGetTestByName(t, "query_sql",
455 | map[string]any{
456 | "query_sql": map[string]any{
457 | "description": "Simple tool to test end to end functionality.",
458 | "authRequired": []any{},
459 | "parameters": []any{
460 | map[string]any{
461 | "authSources": []any{},
462 | "description": "The model containing the explore.",
463 | "name": "model",
464 | "required": true,
465 | "type": "string",
466 | },
467 | map[string]any{
468 | "authSources": []any{},
469 | "description": "The explore to be queried.",
470 | "name": "explore",
471 | "required": true,
472 | "type": "string",
473 | },
474 | map[string]any{
475 | "authSources": []any{},
476 | "description": "The fields to be retrieved.",
477 | "items": map[string]any{
478 | "authSources": []any{},
479 | "description": "A field to be returned in the query",
480 | "name": "field",
481 | "required": true,
482 | "type": "string",
483 | },
484 | "name": "fields",
485 | "required": true,
486 | "type": "array",
487 | },
488 | map[string]any{
489 | "additionalProperties": true,
490 | "authSources": []any{},
491 | "description": "The filters for the query",
492 | "name": "filters",
493 | "required": false,
494 | "type": "object",
495 | },
496 | map[string]any{
497 | "authSources": []any{},
498 | "description": "The query pivots (must be included in fields as well).",
499 | "items": map[string]any{
500 | "authSources": []any{},
501 | "description": "A field to be used as a pivot in the query",
502 | "name": "pivot_field",
503 | "required": false,
504 | "type": "string",
505 | },
506 | "name": "pivots",
507 | "required": false,
508 | "type": "array",
509 | },
510 | map[string]any{
511 | "authSources": []any{},
512 | "description": "The sorts like \"field.id desc 0\".",
513 | "items": map[string]any{
514 | "authSources": []any{},
515 | "description": "A field to be used as a sort in the query",
516 | "name": "sort_field",
517 | "required": false,
518 | "type": "string",
519 | },
520 | "name": "sorts",
521 | "required": false,
522 | "type": "array",
523 | },
524 | map[string]any{
525 | "authSources": []any{},
526 | "description": "The row limit.",
527 | "name": "limit",
528 | "required": false,
529 | "type": "integer",
530 | },
531 | map[string]any{
532 | "authSources": []any{},
533 | "description": "The query timezone.",
534 | "name": "tz",
535 | "required": false,
536 | "type": "string",
537 | },
538 | },
539 | },
540 | },
541 | )
542 | tests.RunToolGetTestByName(t, "query_url",
543 | map[string]any{
544 | "query_url": map[string]any{
545 | "description": "Simple tool to test end to end functionality.",
546 | "authRequired": []any{},
547 | "parameters": []any{
548 | map[string]any{
549 | "authSources": []any{},
550 | "description": "The model containing the explore.",
551 | "name": "model",
552 | "required": true,
553 | "type": "string",
554 | },
555 | map[string]any{
556 | "authSources": []any{},
557 | "description": "The explore to be queried.",
558 | "name": "explore",
559 | "required": true,
560 | "type": "string",
561 | },
562 | map[string]any{
563 | "authSources": []any{},
564 | "description": "The fields to be retrieved.",
565 | "items": map[string]any{
566 | "authSources": []any{},
567 | "description": "A field to be returned in the query",
568 | "name": "field",
569 | "required": true,
570 | "type": "string",
571 | },
572 | "name": "fields",
573 | "required": true,
574 | "type": "array",
575 | },
576 | map[string]any{
577 | "additionalProperties": true,
578 | "authSources": []any{},
579 | "description": "The filters for the query",
580 | "name": "filters",
581 | "required": false,
582 | "type": "object",
583 | },
584 | map[string]any{
585 | "authSources": []any{},
586 | "description": "The query pivots (must be included in fields as well).",
587 | "items": map[string]any{
588 | "authSources": []any{},
589 | "description": "A field to be used as a pivot in the query",
590 | "name": "pivot_field",
591 | "required": false,
592 | "type": "string",
593 | },
594 | "name": "pivots",
595 | "required": false,
596 | "type": "array",
597 | },
598 | map[string]any{
599 | "authSources": []any{},
600 | "description": "The sorts like \"field.id desc 0\".",
601 | "items": map[string]any{
602 | "authSources": []any{},
603 | "description": "A field to be used as a sort in the query",
604 | "name": "sort_field",
605 | "required": false,
606 | "type": "string",
607 | },
608 | "name": "sorts",
609 | "required": false,
610 | "type": "array",
611 | },
612 | map[string]any{
613 | "authSources": []any{},
614 | "description": "The row limit.",
615 | "name": "limit",
616 | "required": false,
617 | "type": "integer",
618 | },
619 | map[string]any{
620 | "authSources": []any{},
621 | "description": "The query timezone.",
622 | "name": "tz",
623 | "required": false,
624 | "type": "string",
625 | },
626 | map[string]any{
627 | "additionalProperties": true,
628 | "authSources": []any{},
629 | "description": "The visualization config for the query",
630 | "name": "vis_config",
631 | "required": false,
632 | "type": "object",
633 | },
634 | },
635 | },
636 | },
637 | )
638 | tests.RunToolGetTestByName(t, "get_looks",
639 | map[string]any{
640 | "get_looks": map[string]any{
641 | "description": "Simple tool to test end to end functionality.",
642 | "authRequired": []any{},
643 | "parameters": []any{
644 | map[string]any{
645 | "authSources": []any{},
646 | "description": "The title of the look.",
647 | "name": "title",
648 | "required": false,
649 | "type": "string",
650 | },
651 | map[string]any{
652 | "authSources": []any{},
653 | "description": "The description of the look.",
654 | "name": "desc",
655 | "required": false,
656 | "type": "string",
657 | },
658 | map[string]any{
659 | "authSources": []any{},
660 | "description": "The number of looks to fetch. Default 100",
661 | "name": "limit",
662 | "required": false,
663 | "type": "integer",
664 | },
665 | map[string]any{
666 | "authSources": []any{},
667 | "description": "The number of looks to skip before fetching. Default 0",
668 | "name": "offset",
669 | "required": false,
670 | "type": "integer",
671 | },
672 | },
673 | },
674 | },
675 | )
676 | tests.RunToolGetTestByName(t, "get_dashboards",
677 | map[string]any{
678 | "get_dashboards": map[string]any{
679 | "description": "Simple tool to test end to end functionality.",
680 | "authRequired": []any{},
681 | "parameters": []any{
682 | map[string]any{
683 | "authSources": []any{},
684 | "description": "The title of the dashboard.",
685 | "name": "title",
686 | "required": false,
687 | "type": "string",
688 | },
689 | map[string]any{
690 | "authSources": []any{},
691 | "description": "The description of the dashboard.",
692 | "name": "desc",
693 | "required": false,
694 | "type": "string",
695 | },
696 | map[string]any{
697 | "authSources": []any{},
698 | "description": "The number of dashboards to fetch. Default 100",
699 | "name": "limit",
700 | "required": false,
701 | "type": "integer",
702 | },
703 | map[string]any{
704 | "authSources": []any{},
705 | "description": "The number of dashboards to skip before fetching. Default 0",
706 | "name": "offset",
707 | "required": false,
708 | "type": "integer",
709 | },
710 | },
711 | },
712 | },
713 | )
714 | tests.RunToolGetTestByName(t, "conversational_analytics",
715 | map[string]any{
716 | "conversational_analytics": map[string]any{
717 | "description": "Simple tool to test end to end functionality.",
718 | "authRequired": []any{},
719 | "parameters": []any{
720 | map[string]any{
721 | "authSources": []any{},
722 | "description": "The user's question, potentially including conversation history and system instructions for context.",
723 | "name": "user_query_with_context",
724 | "required": true,
725 | "type": "string",
726 | },
727 | map[string]any{
728 | "authSources": []any{},
729 | "description": "An Array of at least one and up to 5 explore references like [{'model': 'MODEL_NAME', 'explore': 'EXPLORE_NAME'}]",
730 | "items": map[string]any{
731 | "additionalProperties": true,
732 | "authSources": []any{},
733 | "name": "explore_reference",
734 | "description": "An explore reference like {'model': 'MODEL_NAME', 'explore': 'EXPLORE_NAME'}",
735 | "required": true,
736 | "type": "object",
737 | },
738 | "name": "explore_references",
739 | "required": true,
740 | "type": "array",
741 | },
742 | },
743 | },
744 | },
745 | )
746 | tests.RunToolGetTestByName(t, "health_pulse",
747 | map[string]any{
748 | "health_pulse": map[string]any{
749 | "description": "Checks the health of a Looker instance by running a series of checks on the system.",
750 | "authRequired": []any{},
751 | "parameters": []any{
752 | map[string]any{
753 | "authSources": []any{},
754 | "description": "The health check to run. Can be either: `check_db_connections`, `check_dashboard_performance`,`check_dashboard_errors`,`check_explore_performance`,`check_schedule_failures`, or `check_legacy_features`",
755 | "name": "action",
756 | "required": true,
757 | "type": "string",
758 | },
759 | },
760 | },
761 | },
762 | )
763 | tests.RunToolGetTestByName(t, "health_analyze",
764 | map[string]any{
765 | "health_analyze": map[string]any{
766 | "description": "Provides analysis of a Looker instance's projects, models, or explores.",
767 | "authRequired": []any{},
768 | "parameters": []any{
769 | map[string]any{
770 | "authSources": []any{},
771 | "description": "The analysis to run. Can be 'projects', 'models', or 'explores'.",
772 | "name": "action",
773 | "required": true,
774 | "type": "string",
775 | },
776 | map[string]any{
777 | "authSources": []any{},
778 | "description": "The Looker project to analyze (optional).",
779 | "name": "project",
780 | "required": false,
781 | "type": "string",
782 | },
783 | map[string]any{
784 | "authSources": []any{},
785 | "description": "The Looker model to analyze (optional).",
786 | "name": "model",
787 | "required": false,
788 | "type": "string",
789 | },
790 | map[string]any{
791 | "authSources": []any{},
792 | "description": "The Looker explore to analyze (optional).",
793 | "name": "explore",
794 | "required": false,
795 | "type": "string",
796 | },
797 | map[string]any{
798 | "authSources": []any{},
799 | "description": "The timeframe in days to analyze.",
800 | "name": "timeframe",
801 | "required": false,
802 | "type": "integer",
803 | },
804 | map[string]any{
805 | "authSources": []any{},
806 | "description": "The minimum number of queries for a model or explore to be considered used.",
807 | "name": "min_queries",
808 | "required": false,
809 | "type": "integer",
810 | },
811 | },
812 | },
813 | },
814 | )
815 | tests.RunToolGetTestByName(t, "health_vacuum",
816 | map[string]any{
817 | "health_vacuum": map[string]any{
818 | "description": "Vacuums unused content from a Looker instance.",
819 | "authRequired": []any{},
820 | "parameters": []any{
821 | map[string]any{
822 | "authSources": []any{},
823 | "description": "The vacuum action to run. Can be 'models', or 'explores'.",
824 | "name": "action",
825 | "required": true,
826 | "type": "string",
827 | },
828 | map[string]any{
829 | "authSources": []any{},
830 | "description": "The Looker project to vacuum (optional).",
831 | "name": "project",
832 | "required": false,
833 | "type": "string",
834 | },
835 | map[string]any{
836 | "authSources": []any{},
837 | "description": "The Looker model to vacuum (optional).",
838 | "name": "model",
839 | "required": false,
840 | "type": "string",
841 | },
842 | map[string]any{
843 | "authSources": []any{},
844 | "description": "The Looker explore to vacuum (optional).",
845 | "name": "explore",
846 | "required": false,
847 | "type": "string",
848 | },
849 | map[string]any{
850 | "authSources": []any{},
851 | "description": "The timeframe in days to analyze.",
852 | "name": "timeframe",
853 | "required": false,
854 | "type": "integer",
855 | },
856 | map[string]any{
857 | "authSources": []any{},
858 | "description": "The minimum number of queries for a model or explore to be considered used.",
859 | "name": "min_queries",
860 | "required": false,
861 | "type": "integer",
862 | },
863 | },
864 | },
865 | },
866 | )
867 | tests.RunToolGetTestByName(t, "dev_mode",
868 | map[string]any{
869 | "dev_mode": map[string]any{
870 | "description": "Simple tool to test end to end functionality.",
871 | "authRequired": []any{},
872 | "parameters": []any{
873 | map[string]any{
874 | "authSources": []any{},
875 | "description": "Whether to set Dev Mode.",
876 | "name": "devMode",
877 | "required": false,
878 | "type": "boolean",
879 | },
880 | },
881 | },
882 | },
883 | )
884 | tests.RunToolGetTestByName(t, "get_projects",
885 | map[string]any{
886 | "get_projects": map[string]any{
887 | "description": "Simple tool to test end to end functionality.",
888 | "authRequired": []any{},
889 | "parameters": []any{},
890 | },
891 | },
892 | )
893 | tests.RunToolGetTestByName(t, "get_project_files",
894 | map[string]any{
895 | "get_project_files": map[string]any{
896 | "description": "Simple tool to test end to end functionality.",
897 | "authRequired": []any{},
898 | "parameters": []any{
899 | map[string]any{
900 | "authSources": []any{},
901 | "description": "The id of the project containing the files",
902 | "name": "project_id",
903 | "required": true,
904 | "type": "string",
905 | },
906 | },
907 | },
908 | },
909 | )
910 | tests.RunToolGetTestByName(t, "get_project_file",
911 | map[string]any{
912 | "get_project_file": map[string]any{
913 | "description": "Simple tool to test end to end functionality.",
914 | "authRequired": []any{},
915 | "parameters": []any{
916 | map[string]any{
917 | "authSources": []any{},
918 | "description": "The id of the project containing the files",
919 | "name": "project_id",
920 | "required": true,
921 | "type": "string",
922 | },
923 | map[string]any{
924 | "authSources": []any{},
925 | "description": "The path of the file within the project",
926 | "name": "file_path",
927 | "required": true,
928 | "type": "string",
929 | },
930 | },
931 | },
932 | },
933 | )
934 | tests.RunToolGetTestByName(t, "create_project_file",
935 | map[string]any{
936 | "create_project_file": map[string]any{
937 | "description": "Simple tool to test end to end functionality.",
938 | "authRequired": []any{},
939 | "parameters": []any{
940 | map[string]any{
941 | "authSources": []any{},
942 | "description": "The id of the project containing the files",
943 | "name": "project_id",
944 | "required": true,
945 | "type": "string",
946 | },
947 | map[string]any{
948 | "authSources": []any{},
949 | "description": "The path of the file within the project",
950 | "name": "file_path",
951 | "required": true,
952 | "type": "string",
953 | },
954 | map[string]any{
955 | "authSources": []any{},
956 | "description": "The content of the file",
957 | "name": "file_content",
958 | "required": true,
959 | "type": "string",
960 | },
961 | },
962 | },
963 | },
964 | )
965 | tests.RunToolGetTestByName(t, "update_project_file",
966 | map[string]any{
967 | "update_project_file": map[string]any{
968 | "description": "Simple tool to test end to end functionality.",
969 | "authRequired": []any{},
970 | "parameters": []any{
971 | map[string]any{
972 | "authSources": []any{},
973 | "description": "The id of the project containing the files",
974 | "name": "project_id",
975 | "required": true,
976 | "type": "string",
977 | },
978 | map[string]any{
979 | "authSources": []any{},
980 | "description": "The path of the file within the project",
981 | "name": "file_path",
982 | "required": true,
983 | "type": "string",
984 | },
985 | map[string]any{
986 | "authSources": []any{},
987 | "description": "The content of the file",
988 | "name": "file_content",
989 | "required": true,
990 | "type": "string",
991 | },
992 | },
993 | },
994 | },
995 | )
996 | tests.RunToolGetTestByName(t, "delete_project_file",
997 | map[string]any{
998 | "delete_project_file": map[string]any{
999 | "description": "Simple tool to test end to end functionality.",
1000 | "authRequired": []any{},
1001 | "parameters": []any{
1002 | map[string]any{
1003 | "authSources": []any{},
1004 | "description": "The id of the project containing the files",
1005 | "name": "project_id",
1006 | "required": true,
1007 | "type": "string",
1008 | },
1009 | map[string]any{
1010 | "authSources": []any{},
1011 | "description": "The path of the file within the project",
1012 | "name": "file_path",
1013 | "required": true,
1014 | "type": "string",
1015 | },
1016 | },
1017 | },
1018 | },
1019 | )
1020 | tests.RunToolGetTestByName(t, "get_connections",
1021 | map[string]any{
1022 | "get_connections": map[string]any{
1023 | "description": "Simple tool to test end to end functionality.",
1024 | "authRequired": []any{},
1025 | "parameters": []any{},
1026 | },
1027 | },
1028 | )
1029 | tests.RunToolGetTestByName(t, "get_connection_schemas",
1030 | map[string]any{
1031 | "get_connection_schemas": map[string]any{
1032 | "description": "Simple tool to test end to end functionality.",
1033 | "authRequired": []any{},
1034 | "parameters": []any{
1035 | map[string]any{
1036 | "authSources": []any{},
1037 | "description": "The connection containing the schemas.",
1038 | "name": "conn",
1039 | "required": true,
1040 | "type": "string",
1041 | },
1042 | map[string]any{
1043 | "authSources": []any{},
1044 | "description": "The optional database to search",
1045 | "name": "db",
1046 | "required": false,
1047 | "type": "string",
1048 | },
1049 | },
1050 | },
1051 | },
1052 | )
1053 | tests.RunToolGetTestByName(t, "get_connection_databases",
1054 | map[string]any{
1055 | "get_connection_databases": map[string]any{
1056 | "description": "Simple tool to test end to end functionality.",
1057 | "authRequired": []any{},
1058 | "parameters": []any{
1059 | map[string]any{
1060 | "authSources": []any{},
1061 | "description": "The connection containing the databases.",
1062 | "name": "conn",
1063 | "required": true,
1064 | "type": "string",
1065 | },
1066 | },
1067 | },
1068 | },
1069 | )
1070 | tests.RunToolGetTestByName(t, "get_connection_tables",
1071 | map[string]any{
1072 | "get_connection_tables": map[string]any{
1073 | "description": "Simple tool to test end to end functionality.",
1074 | "authRequired": []any{},
1075 | "parameters": []any{
1076 | map[string]any{
1077 | "authSources": []any{},
1078 | "description": "The connection containing the tables.",
1079 | "name": "conn",
1080 | "required": true,
1081 | "type": "string",
1082 | },
1083 | map[string]any{
1084 | "authSources": []any{},
1085 | "description": "The optional database to search",
1086 | "name": "db",
1087 | "required": false,
1088 | "type": "string",
1089 | },
1090 | map[string]any{
1091 | "authSources": []any{},
1092 | "description": "The schema containing the tables.",
1093 | "name": "schema",
1094 | "required": true,
1095 | "type": "string",
1096 | },
1097 | },
1098 | },
1099 | },
1100 | )
1101 | tests.RunToolGetTestByName(t, "get_connection_table_columns",
1102 | map[string]any{
1103 | "get_connection_table_columns": map[string]any{
1104 | "description": "Simple tool to test end to end functionality.",
1105 | "authRequired": []any{},
1106 | "parameters": []any{
1107 | map[string]any{
1108 | "authSources": []any{},
1109 | "description": "The connection containing the tables.",
1110 | "name": "conn",
1111 | "required": true,
1112 | "type": "string",
1113 | },
1114 | map[string]any{
1115 | "authSources": []any{},
1116 | "description": "The optional database to search",
1117 | "name": "db",
1118 | "required": false,
1119 | "type": "string",
1120 | },
1121 | map[string]any{
1122 | "authSources": []any{},
1123 | "description": "The schema containing the tables.",
1124 | "name": "schema",
1125 | "required": true,
1126 | "type": "string",
1127 | },
1128 | map[string]any{
1129 | "authSources": []any{},
1130 | "description": "A comma separated list of tables containing the columns.",
1131 | "name": "tables",
1132 | "required": true,
1133 | "type": "string",
1134 | },
1135 | },
1136 | },
1137 | },
1138 | )
1139 |
1140 | wantResult := "{\"connections\":[],\"label\":\"System Activity\",\"name\":\"system__activity\",\"project_name\":\"system__activity\"}"
1141 | tests.RunToolInvokeSimpleTest(t, "get_models", wantResult)
1142 |
1143 | wantResult = "{\"description\":\"Data about Look and dashboard usage, including frequency of views, favoriting, scheduling, embedding, and access via the API. Also includes details about individual Looks and dashboards.\",\"group_label\":\"System Activity\",\"label\":\"Content Usage\",\"name\":\"content_usage\"}"
1144 | tests.RunToolInvokeParametersTest(t, "get_explores", []byte(`{"model": "system__activity"}`), wantResult)
1145 |
1146 | wantResult = "{\"description\":\"Number of times this content has been viewed via the Looker API\",\"label\":\"Content Usage API Count\",\"label_short\":\"API Count\",\"name\":\"content_usage.api_count\",\"type\":\"number\"}"
1147 | tests.RunToolInvokeParametersTest(t, "get_dimensions", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
1148 |
1149 | wantResult = "{\"description\":\"The total number of views via the Looker API\",\"label\":\"Content Usage API Total\",\"label_short\":\"API Total\",\"name\":\"content_usage.api_total\",\"type\":\"sum\"}"
1150 | tests.RunToolInvokeParametersTest(t, "get_measures", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
1151 |
1152 | wantResult = "[]"
1153 | tests.RunToolInvokeParametersTest(t, "get_filters", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
1154 |
1155 | wantResult = "[]"
1156 | tests.RunToolInvokeParametersTest(t, "get_parameters", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
1157 |
1158 | wantResult = "{\"look.count\":"
1159 | tests.RunToolInvokeParametersTest(t, "query", []byte(`{"model": "system__activity", "explore": "look", "fields": ["look.count"]}`), wantResult)
1160 |
1161 | wantResult = "SELECT"
1162 | tests.RunToolInvokeParametersTest(t, "query_sql", []byte(`{"model": "system__activity", "explore": "look", "fields": ["look.count"]}`), wantResult)
1163 |
1164 | wantResult = "system__activity"
1165 | tests.RunToolInvokeParametersTest(t, "query_url", []byte(`{"model": "system__activity", "explore": "look", "fields": ["look.count"]}`), wantResult)
1166 |
1167 | // A system that is just being used for testing has no looks or dashboards
1168 | wantResult = "null"
1169 | tests.RunToolInvokeParametersTest(t, "get_looks", []byte(`{"title": "FOO", "desc": "BAR"}`), wantResult)
1170 |
1171 | wantResult = "null"
1172 | tests.RunToolInvokeParametersTest(t, "get_dashboards", []byte(`{"title": "FOO", "desc": "BAR"}`), wantResult)
1173 |
1174 | runConversationalAnalytics(t, "system__activity", "content_usage")
1175 |
1176 | wantResult = "\"Connection\":\"thelook\""
1177 | tests.RunToolInvokeParametersTest(t, "health_pulse", []byte(`{"action": "check_db_connections"}`), wantResult)
1178 |
1179 | wantResult = "[]"
1180 | tests.RunToolInvokeParametersTest(t, "health_pulse", []byte(`{"action": "check_schedule_failures"}`), wantResult)
1181 |
1182 | wantResult = "[{\"Feature\":\"Unsupported in Looker (Google Cloud core)\"}]"
1183 | tests.RunToolInvokeParametersTest(t, "health_pulse", []byte(`{"action": "check_legacy_features"}`), wantResult)
1184 |
1185 | wantResult = "\"Project\":\"the_look\""
1186 | tests.RunToolInvokeParametersTest(t, "health_analyze", []byte(`{"action": "projects"}`), wantResult)
1187 |
1188 | wantResult = "\"Model\":\"the_look\""
1189 | tests.RunToolInvokeParametersTest(t, "health_analyze", []byte(`{"action": "explores", "project": "the_look", "model": "the_look", "explore": "inventory_items"}`), wantResult)
1190 |
1191 | wantResult = "\"Model\":\"the_look\""
1192 | tests.RunToolInvokeParametersTest(t, "health_vacuum", []byte(`{"action": "models"}`), wantResult)
1193 |
1194 | wantResult = "the_look"
1195 | tests.RunToolInvokeSimpleTest(t, "get_projects", wantResult)
1196 |
1197 | wantResult = "order_items.view"
1198 | tests.RunToolInvokeParametersTest(t, "get_project_files", []byte(`{"project_id": "the_look"}`), wantResult)
1199 |
1200 | wantResult = "view"
1201 | tests.RunToolInvokeParametersTest(t, "get_project_file", []byte(`{"project_id": "the_look", "file_path": "order_items.view.lkml"}`), wantResult)
1202 |
1203 | wantResult = "dev"
1204 | tests.RunToolInvokeParametersTest(t, "dev_mode", []byte(`{"devMode": true}`), wantResult)
1205 |
1206 | wantResult = "created"
1207 | tests.RunToolInvokeParametersTest(t, "create_project_file", []byte(`{"project_id": "the_look", "file_path": "foo.view.lkml", "file_content": "view"}`), wantResult)
1208 |
1209 | wantResult = "updated"
1210 | tests.RunToolInvokeParametersTest(t, "update_project_file", []byte(`{"project_id": "the_look", "file_path": "foo.view.lkml", "file_content": "model"}`), wantResult)
1211 |
1212 | wantResult = "deleted"
1213 | tests.RunToolInvokeParametersTest(t, "delete_project_file", []byte(`{"project_id": "the_look", "file_path": "foo.view.lkml"}`), wantResult)
1214 |
1215 | wantResult = "production"
1216 | tests.RunToolInvokeParametersTest(t, "dev_mode", []byte(`{"devMode": false}`), wantResult)
1217 |
1218 | wantResult = "thelook"
1219 | tests.RunToolInvokeSimpleTest(t, "get_connections", wantResult)
1220 |
1221 | wantResult = "{\"name\":\"demo_db\",\"is_default\":true}"
1222 | tests.RunToolInvokeParametersTest(t, "get_connection_schemas", []byte(`{"conn": "thelook"}`), wantResult)
1223 |
1224 | wantResult = "[]"
1225 | tests.RunToolInvokeParametersTest(t, "get_connection_databases", []byte(`{"conn": "thelook"}`), wantResult)
1226 |
1227 | wantResult = "Employees"
1228 | tests.RunToolInvokeParametersTest(t, "get_connection_tables", []byte(`{"conn": "thelook", "schema": "demo_db"}`), wantResult)
1229 |
1230 | wantResult = "{\"column_name\":\"EmpID\",\"data_type_database\":\"int\",\"data_type_looker\":\"number\",\"sql_escaped_column_name\":\"EmpID\"}"
1231 | tests.RunToolInvokeParametersTest(t, "get_connection_table_columns", []byte(`{"conn": "thelook", "schema": "demo_db", "tables": "Employees"}`), wantResult)
1232 | }
1233 |
1234 | func runConversationalAnalytics(t *testing.T, modelName, exploreName string) {
1235 | exploreRefsJSON := fmt.Sprintf(`[{"model":"%s","explore":"%s"}]`, modelName, exploreName)
1236 |
1237 | var refs []map[string]any
1238 | if err := json.Unmarshal([]byte(exploreRefsJSON), &refs); err != nil {
1239 | t.Fatalf("failed to unmarshal explore refs: %v", err)
1240 | }
1241 |
1242 | testCases := []struct {
1243 | name string
1244 | exploreRefs []map[string]any
1245 | wantStatusCode int
1246 | wantInResult string
1247 | wantInError string
1248 | }{
1249 | {
1250 | name: "invoke conversational analytics with explore",
1251 | exploreRefs: refs,
1252 | wantStatusCode: http.StatusOK,
1253 | wantInResult: `Answer`,
1254 | },
1255 | }
1256 |
1257 | for _, tc := range testCases {
1258 | t.Run(tc.name, func(t *testing.T) {
1259 | requestBodyMap := map[string]any{
1260 | "user_query_with_context": "What is in the explore?",
1261 | "explore_references": tc.exploreRefs,
1262 | }
1263 | bodyBytes, err := json.Marshal(requestBodyMap)
1264 | if err != nil {
1265 | t.Fatalf("failed to marshal request body: %v", err)
1266 | }
1267 | url := "http://127.0.0.1:5000/api/tool/conversational_analytics/invoke"
1268 | resp, bodyBytes := tests.RunRequest(t, http.MethodPost, url, bytes.NewBuffer(bodyBytes), nil)
1269 |
1270 | if resp.StatusCode != tc.wantStatusCode {
1271 | t.Fatalf("unexpected status code: got %d, want %d. Body: %s", resp.StatusCode, tc.wantStatusCode, string(bodyBytes))
1272 | }
1273 |
1274 | if tc.wantInResult != "" {
1275 | var respBody map[string]interface{}
1276 | if err := json.Unmarshal(bodyBytes, &respBody); err != nil {
1277 | t.Fatalf("error parsing response body: %v", err)
1278 | }
1279 | got, ok := respBody["result"].(string)
1280 | if !ok {
1281 | t.Fatalf("unable to find result in response body")
1282 | }
1283 | if !strings.Contains(got, tc.wantInResult) {
1284 | t.Errorf("unexpected result: got %q, want to contain %q", got, tc.wantInResult)
1285 | }
1286 | }
1287 |
1288 | if tc.wantInError != "" {
1289 | if !strings.Contains(string(bodyBytes), tc.wantInError) {
1290 | t.Errorf("unexpected error message: got %q, want to contain %q", string(bodyBytes), tc.wantInError)
1291 | }
1292 | }
1293 | })
1294 | }
1295 | }
1296 |
```
--------------------------------------------------------------------------------
/tests/clickhouse/clickhouse_integration_test.go:
--------------------------------------------------------------------------------
```go
1 | // Copyright 2025 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package clickhouse
16 |
17 | import (
18 | "context"
19 | "database/sql"
20 | "fmt"
21 | "os"
22 | "regexp"
23 | "strings"
24 | "testing"
25 | "time"
26 |
27 | _ "github.com/ClickHouse/clickhouse-go/v2"
28 | "github.com/google/uuid"
29 | "github.com/googleapis/genai-toolbox/internal/sources"
30 | "github.com/googleapis/genai-toolbox/internal/sources/clickhouse"
31 | "github.com/googleapis/genai-toolbox/internal/testutils"
32 | "github.com/googleapis/genai-toolbox/internal/tools"
33 | clickhouseexecutesql "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhouseexecutesql"
34 | clickhouselistdatabases "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhouselistdatabases"
35 | clickhouselisttables "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhouselisttables"
36 | clickhousesql "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhousesql"
37 | "github.com/googleapis/genai-toolbox/tests"
38 | "go.opentelemetry.io/otel/trace/noop"
39 | )
40 |
41 | var (
42 | ClickHouseSourceKind = "clickhouse"
43 | ClickHouseToolKind = "clickhouse-sql"
44 | ClickHouseDatabase = os.Getenv("CLICKHOUSE_DATABASE")
45 | ClickHouseHost = os.Getenv("CLICKHOUSE_HOST")
46 | ClickHousePort = os.Getenv("CLICKHOUSE_PORT")
47 | ClickHouseUser = os.Getenv("CLICKHOUSE_USER")
48 | ClickHousePass = os.Getenv("CLICKHOUSE_PASS")
49 | ClickHouseProtocol = os.Getenv("CLICKHOUSE_PROTOCOL")
50 | )
51 |
52 | func getClickHouseVars(t *testing.T) map[string]any {
53 | switch "" {
54 | case ClickHouseHost:
55 | t.Skip("'CLICKHOUSE_HOST' not set")
56 | case ClickHousePort:
57 | t.Skip("'CLICKHOUSE_PORT' not set")
58 | case ClickHouseUser:
59 | t.Skip("'CLICKHOUSE_USER' not set")
60 | }
61 |
62 | // Set defaults for optional parameters
63 | if ClickHouseDatabase == "" {
64 | ClickHouseDatabase = "default"
65 | }
66 | if ClickHouseProtocol == "" {
67 | ClickHouseProtocol = "http"
68 | }
69 |
70 | return map[string]any{
71 | "kind": ClickHouseSourceKind,
72 | "host": ClickHouseHost,
73 | "port": ClickHousePort,
74 | "database": ClickHouseDatabase,
75 | "user": ClickHouseUser,
76 | "password": ClickHousePass,
77 | "protocol": ClickHouseProtocol,
78 | "secure": false,
79 | }
80 | }
81 |
82 | // initClickHouseConnectionPool creates a ClickHouse connection using HTTP protocol only.
83 | // Note: ClickHouse tools in this codebase only support HTTP/HTTPS protocols, not the native protocol.
84 | // Typical setup: localhost:8123 (HTTP) or localhost:8443 (HTTPS)
85 | func initClickHouseConnectionPool(host, port, user, pass, dbname, protocol string) (*sql.DB, error) {
86 | if protocol == "" {
87 | protocol = "https"
88 | }
89 |
90 | var dsn string
91 | switch protocol {
92 | case "http":
93 | dsn = fmt.Sprintf("http://%s:%s@%s:%s/%s", user, pass, host, port, dbname)
94 | case "https":
95 | dsn = fmt.Sprintf("https://%s:%s@%s:%s/%s?secure=true&skip_verify=false", user, pass, host, port, dbname)
96 | default:
97 | dsn = fmt.Sprintf("https://%s:%s@%s:%s/%s?secure=true&skip_verify=false", user, pass, host, port, dbname)
98 | }
99 |
100 | pool, err := sql.Open("clickhouse", dsn)
101 | if err != nil {
102 | return nil, fmt.Errorf("sql.Open: %w", err)
103 | }
104 |
105 | return pool, nil
106 | }
107 |
108 | func TestClickHouse(t *testing.T) {
109 | sourceConfig := getClickHouseVars(t)
110 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
111 | defer cancel()
112 |
113 | var args []string
114 |
115 | pool, err := initClickHouseConnectionPool(ClickHouseHost, ClickHousePort, ClickHouseUser, ClickHousePass, ClickHouseDatabase, ClickHouseProtocol)
116 | if err != nil {
117 | t.Fatalf("unable to create ClickHouse connection pool: %s", err)
118 | }
119 | defer pool.Close()
120 |
121 | tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
122 | tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
123 | tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
124 |
125 | createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getClickHouseSQLParamToolInfo(tableNameParam)
126 | teardownTable1 := setupClickHouseSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
127 | defer teardownTable1(t)
128 |
129 | createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getClickHouseSQLAuthToolInfo(tableNameAuth)
130 | teardownTable2 := setupClickHouseSQLTable(t, ctx, pool, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
131 | defer teardownTable2(t)
132 |
133 | toolsFile := tests.GetToolsConfig(sourceConfig, ClickHouseToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
134 | toolsFile = addClickHouseExecuteSqlConfig(t, toolsFile)
135 | tmplSelectCombined, tmplSelectFilterCombined := getClickHouseSQLTmplToolStatement()
136 | toolsFile = addClickHouseTemplateParamConfig(t, toolsFile, ClickHouseToolKind, tmplSelectCombined, tmplSelectFilterCombined)
137 |
138 | cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
139 | if err != nil {
140 | t.Fatalf("command initialization returned an error: %s", err)
141 | }
142 | defer cleanup()
143 |
144 | waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
145 | defer cancel()
146 | out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
147 | if err != nil {
148 | t.Logf("toolbox command logs: \n%s", out)
149 | t.Fatalf("toolbox didn't start successfully: %s", err)
150 | }
151 |
152 | // Get configs for tests
153 | select1Want, mcpSelect1Want, mcpMyFailToolWant, createTableStatement, nilIdWant := getClickHouseWants()
154 |
155 | // Run tests
156 | tests.RunToolGetTest(t)
157 | tests.RunToolInvokeTest(t, select1Want, tests.WithMyToolById4Want(nilIdWant))
158 | tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
159 | tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want)
160 | tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam)
161 | }
162 |
163 | func addClickHouseExecuteSqlConfig(t *testing.T, config map[string]any) map[string]any {
164 | tools, ok := config["tools"].(map[string]any)
165 | if !ok {
166 | t.Fatalf("unable to get tools from config")
167 | }
168 | tools["my-exec-sql-tool"] = map[string]any{
169 | "kind": "clickhouse-execute-sql",
170 | "source": "my-instance",
171 | "description": "Tool to execute sql",
172 | }
173 | tools["my-auth-exec-sql-tool"] = map[string]any{
174 | "kind": "clickhouse-execute-sql",
175 | "source": "my-instance",
176 | "description": "Tool to execute sql",
177 | "authRequired": []string{
178 | "my-google-auth",
179 | },
180 | }
181 | config["tools"] = tools
182 | return config
183 | }
184 |
185 | func addClickHouseTemplateParamConfig(t *testing.T, config map[string]any, toolKind, tmplSelectCombined, tmplSelectFilterCombined string) map[string]any {
186 | toolsMap, ok := config["tools"].(map[string]any)
187 | if !ok {
188 | t.Fatalf("unable to get tools from config")
189 | }
190 |
191 | // ClickHouse-specific template parameter tools with compatible syntax
192 | toolsMap["create-table-templateParams-tool"] = map[string]any{
193 | "kind": toolKind,
194 | "source": "my-instance",
195 | "description": "Create table tool with template parameters",
196 | "statement": "CREATE TABLE {{.tableName}} ({{array .columns}}) ORDER BY id",
197 | "templateParameters": []tools.Parameter{
198 | tools.NewStringParameter("tableName", "some description"),
199 | tools.NewArrayParameter("columns", "The columns to create", tools.NewStringParameter("column", "A column name that will be created")),
200 | },
201 | }
202 | toolsMap["insert-table-templateParams-tool"] = map[string]any{
203 | "kind": toolKind,
204 | "source": "my-instance",
205 | "description": "Insert table tool with template parameters",
206 | "statement": "INSERT INTO {{.tableName}} ({{array .columns}}) VALUES ({{.values}})",
207 | "templateParameters": []tools.Parameter{
208 | tools.NewStringParameter("tableName", "some description"),
209 | tools.NewArrayParameter("columns", "The columns to insert into", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
210 | tools.NewStringParameter("values", "The values to insert as a comma separated string"),
211 | },
212 | }
213 | toolsMap["select-templateParams-tool"] = map[string]any{
214 | "kind": toolKind,
215 | "source": "my-instance",
216 | "description": "Select table tool with template parameters",
217 | "statement": "SELECT id AS \"id\", name AS \"name\", age AS \"age\" FROM {{.tableName}} ORDER BY id",
218 | "templateParameters": []tools.Parameter{
219 | tools.NewStringParameter("tableName", "some description"),
220 | },
221 | }
222 | toolsMap["select-templateParams-combined-tool"] = map[string]any{
223 | "kind": toolKind,
224 | "source": "my-instance",
225 | "description": "Select table tool with combined template parameters",
226 | "statement": tmplSelectCombined,
227 | "parameters": []tools.Parameter{
228 | tools.NewIntParameter("id", "the id of the user"),
229 | },
230 | "templateParameters": []tools.Parameter{
231 | tools.NewStringParameter("tableName", "some description"),
232 | },
233 | }
234 | toolsMap["select-fields-templateParams-tool"] = map[string]any{
235 | "kind": toolKind,
236 | "source": "my-instance",
237 | "description": "Select specific fields tool with template parameters",
238 | "statement": "SELECT name AS \"name\" FROM {{.tableName}} ORDER BY id",
239 | "templateParameters": []tools.Parameter{
240 | tools.NewStringParameter("tableName", "some description"),
241 | },
242 | }
243 | toolsMap["select-filter-templateParams-combined-tool"] = map[string]any{
244 | "kind": toolKind,
245 | "source": "my-instance",
246 | "description": "Select table tool with filter template parameters",
247 | "statement": tmplSelectFilterCombined,
248 | "parameters": []tools.Parameter{
249 | tools.NewStringParameter("name", "the name to filter by"),
250 | },
251 | "templateParameters": []tools.Parameter{
252 | tools.NewStringParameter("tableName", "some description"),
253 | tools.NewStringParameter("columnFilter", "some description"),
254 | },
255 | }
256 | // Firebird uses simple DROP TABLE syntax without IF EXISTS
257 | toolsMap["drop-table-templateParams-tool"] = map[string]any{
258 | "kind": toolKind,
259 | "source": "my-instance",
260 | "description": "Drop table tool with template parameters",
261 | "statement": "DROP TABLE {{.tableName}}",
262 | "templateParameters": []tools.Parameter{
263 | tools.NewStringParameter("tableName", "some description"),
264 | },
265 | }
266 | config["tools"] = toolsMap
267 | return config
268 | }
269 |
270 | func TestClickHouseBasicConnection(t *testing.T) {
271 | sourceConfig := getClickHouseVars(t)
272 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
273 | defer cancel()
274 |
275 | var args []string
276 |
277 | pool, err := initClickHouseConnectionPool(ClickHouseHost, ClickHousePort, ClickHouseUser, ClickHousePass, ClickHouseDatabase, ClickHouseProtocol)
278 | if err != nil {
279 | t.Fatalf("unable to create ClickHouse connection pool: %s", err)
280 | }
281 | defer pool.Close()
282 |
283 | // Test basic connection
284 | err = pool.PingContext(ctx)
285 | if err != nil {
286 | t.Fatalf("unable to ping ClickHouse: %s", err)
287 | }
288 |
289 | // Test basic query
290 | rows, err := pool.QueryContext(ctx, "SELECT 1 as test_value")
291 | if err != nil {
292 | t.Fatalf("unable to execute basic query: %s", err)
293 | }
294 | defer rows.Close()
295 |
296 | if !rows.Next() {
297 | t.Fatalf("expected at least one row from basic query")
298 | }
299 |
300 | var testValue int
301 | err = rows.Scan(&testValue)
302 | if err != nil {
303 | t.Fatalf("unable to scan result: %s", err)
304 | }
305 |
306 | if testValue != 1 {
307 | t.Fatalf("expected test_value to be 1, got %d", testValue)
308 | }
309 |
310 | // Write a basic tools config and test the server endpoint (without auth services)
311 | toolsFile := map[string]any{
312 | "sources": map[string]any{
313 | "my-instance": sourceConfig,
314 | },
315 | "tools": map[string]any{
316 | "my-simple-tool": map[string]any{
317 | "kind": ClickHouseToolKind,
318 | "source": "my-instance",
319 | "description": "Simple tool to test end to end functionality.",
320 | "statement": "SELECT 1;",
321 | },
322 | },
323 | }
324 |
325 | cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
326 | if err != nil {
327 | t.Fatalf("command initialization returned an error: %s", err)
328 | }
329 | defer cleanup()
330 |
331 | waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
332 | defer cancel()
333 | out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
334 | if err != nil {
335 | t.Logf("toolbox command logs: \n%s", out)
336 | t.Fatalf("toolbox didn't start successfully: %s", err)
337 | }
338 |
339 | tests.RunToolGetTest(t)
340 | t.Logf("✅ ClickHouse basic connection test completed successfully")
341 | }
342 |
343 | func getClickHouseWants() (string, string, string, string, string) {
344 | select1Want := "[{\"1\":1}]"
345 | mcpSelect1Want := `{"jsonrpc":"2.0","id":"invoke my-auth-required-tool","result":{"content":[{"type":"text","text":"{\"1\":1}"}]}}`
346 | mcpMyFailToolWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: sendQuery: [HTTP 400] response body: \"Code: 62. DB::Exception: Syntax error: failed at position 1 (SELEC): SELEC 1;. Expected one of: Query, Query with output, EXPLAIN, EXPLAIN, SELECT query, possibly with UNION, list of union elements, SELECT query, subquery, possibly with UNION, SELECT subquery, SELECT query, WITH, FROM, SELECT, SHOW CREATE QUOTA query, SHOW CREATE, SHOW [FULL] [TEMPORARY] TABLES|DATABASES|CLUSTERS|CLUSTER|MERGES 'name' [[NOT] [I]LIKE 'str'] [LIMIT expr], SHOW, SHOW COLUMNS query, SHOW ENGINES query, SHOW ENGINES, SHOW FUNCTIONS query, SHOW FUNCTIONS, SHOW INDEXES query, SHOW SETTING query, SHOW SETTING, EXISTS or SHOW CREATE query, EXISTS, DESCRIBE FILESYSTEM CACHE query, DESCRIBE, DESC, DESCRIBE query, SHOW PROCESSLIST query, SHOW PROCESSLIST, CREATE TABLE or ATTACH TABLE query, CREATE, ATTACH, REPLACE, CREATE DATABASE query, CREATE VIEW query, CREATE DICTIONARY, CREATE LIVE VIEW query, CREATE WINDOW VIEW query, ALTER query, ALTER TABLE, ALTER TEMPORARY TABLE, ALTER DATABASE, RENAME query, RENAME DATABASE, RENAME TABLE, EXCHANGE TABLES, RENAME DICTIONARY, EXCHANGE DICTIONARIES, RENAME, DROP query, DROP, DETACH, TRUNCATE, UNDROP query, UNDROP, CHECK ALL TABLES, CHECK TABLE, KILL QUERY query, KILL, OPTIMIZE query, OPTIMIZE TABLE, WATCH query, WATCH, SHOW ACCESS query, SHOW ACCESS, ShowAccessEntitiesQuery, SHOW GRANTS query, SHOW GRANTS, SHOW PRIVILEGES query, SHOW PRIVILEGES, BACKUP or RESTORE query, BACKUP, RESTORE, INSERT query, INSERT INTO, USE query, USE, SET ROLE or SET DEFAULT ROLE query, SET ROLE DEFAULT, SET ROLE, SET DEFAULT ROLE, SET query, SET, SYSTEM query, SYSTEM, CREATE USER or ALTER USER query, ALTER USER, CREATE USER, CREATE ROLE or ALTER ROLE query, ALTER ROLE, CREATE ROLE, CREATE QUOTA or ALTER QUOTA query, ALTER QUOTA, CREATE QUOTA, CREATE ROW POLICY or ALTER ROW POLICY query, ALTER POLICY, ALTER ROW POLICY, CREATE POLICY, CREATE ROW POLICY, CREATE SETTINGS PROFILE or ALTER SETTINGS PROFILE query, ALTER SETTINGS PROFILE, ALTER PROFILE, CREATE SETTINGS PROFILE, CREATE PROFILE, CREATE FUNCTION query, DROP FUNCTION query, CREATE WORKLOAD query, DROP WORKLOAD query, CREATE RESOURCE query, DROP RESOURCE query, CREATE NAMED COLLECTION, DROP NAMED COLLECTION query, Alter NAMED COLLECTION query, ALTER, CREATE INDEX query, DROP INDEX query, DROP access entity query, MOVE access entity query, MOVE, GRANT or REVOKE query, REVOKE, GRANT, CHECK GRANT, CHECK GRANT, EXTERNAL DDL query, EXTERNAL DDL FROM, TCL query, BEGIN TRANSACTION, START TRANSACTION, COMMIT, ROLLBACK, SET TRANSACTION SNAPSHOT, Delete query, DELETE, Update query, UPDATE. (SYNTAX_ERROR) (version 25.7.5.34 (official build))\n\""}],"isError":true}}`
347 | createTableStatement := `"CREATE TABLE t (id UInt32, name String) ENGINE = Memory"`
348 | nullWant := `[{"id":4,"name":""}]`
349 | return select1Want, mcpSelect1Want, mcpMyFailToolWant, createTableStatement, nullWant
350 | }
351 |
352 | func TestClickHouseSQLTool(t *testing.T) {
353 | _ = getClickHouseVars(t)
354 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
355 | defer cancel()
356 |
357 | pool, err := initClickHouseConnectionPool(ClickHouseHost, ClickHousePort, ClickHouseUser, ClickHousePass, ClickHouseDatabase, ClickHouseProtocol)
358 | if err != nil {
359 | t.Fatalf("unable to create ClickHouse connection pool: %s", err)
360 | }
361 | defer pool.Close()
362 |
363 | tableName := "test_sql_" + strings.ReplaceAll(uuid.New().String(), "-", "")
364 | createTableSQL := fmt.Sprintf(`
365 | CREATE TABLE %s (
366 | id UInt32,
367 | name String,
368 | age UInt8,
369 | created_at DateTime DEFAULT now()
370 | ) ENGINE = Memory
371 | `, tableName)
372 |
373 | _, err = pool.ExecContext(ctx, createTableSQL)
374 | if err != nil {
375 | t.Fatalf("Failed to create test table: %v", err)
376 | }
377 | defer func() {
378 | _, _ = pool.ExecContext(ctx, fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName))
379 | }()
380 |
381 | insertSQL := fmt.Sprintf("INSERT INTO %s (id, name, age) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)", tableName)
382 | _, err = pool.ExecContext(ctx, insertSQL, 1, "Alice", 25, 2, "Bob", 30, 3, "Charlie", 35)
383 | if err != nil {
384 | t.Fatalf("Failed to insert test data: %v", err)
385 | }
386 |
387 | t.Run("SimpleSelect", func(t *testing.T) {
388 | toolConfig := clickhousesql.Config{
389 | Name: "test-select",
390 | Kind: "clickhouse-sql",
391 | Source: "test-clickhouse",
392 | Description: "Test select query",
393 | Statement: fmt.Sprintf("SELECT * FROM %s ORDER BY id", tableName),
394 | }
395 |
396 | source := createMockSource(t, pool)
397 | sourcesMap := map[string]sources.Source{
398 | "test-clickhouse": source,
399 | }
400 |
401 | tool, err := toolConfig.Initialize(sourcesMap)
402 | if err != nil {
403 | t.Fatalf("Failed to initialize tool: %v", err)
404 | }
405 |
406 | result, err := tool.Invoke(ctx, tools.ParamValues{}, "")
407 | if err != nil {
408 | t.Fatalf("Failed to invoke tool: %v", err)
409 | }
410 |
411 | resultSlice, ok := result.([]any)
412 | if !ok {
413 | t.Fatalf("Expected result to be []any, got %T", result)
414 | }
415 |
416 | if len(resultSlice) != 3 {
417 | t.Errorf("Expected 3 results, got %d", len(resultSlice))
418 | }
419 | })
420 |
421 | t.Run("ParameterizedQuery", func(t *testing.T) {
422 | toolConfig := clickhousesql.Config{
423 | Name: "test-param-query",
424 | Kind: "clickhouse-sql",
425 | Source: "test-clickhouse",
426 | Description: "Test parameterized query",
427 | Statement: fmt.Sprintf("SELECT * FROM %s WHERE age > ? ORDER BY id", tableName),
428 | Parameters: tools.Parameters{
429 | tools.NewIntParameter("min_age", "Minimum age"),
430 | },
431 | }
432 |
433 | source := createMockSource(t, pool)
434 | sourcesMap := map[string]sources.Source{
435 | "test-clickhouse": source,
436 | }
437 |
438 | tool, err := toolConfig.Initialize(sourcesMap)
439 | if err != nil {
440 | t.Fatalf("Failed to initialize tool: %v", err)
441 | }
442 |
443 | params := tools.ParamValues{
444 | {Name: "min_age", Value: 28},
445 | }
446 |
447 | result, err := tool.Invoke(ctx, params, "")
448 | if err != nil {
449 | t.Fatalf("Failed to invoke tool: %v", err)
450 | }
451 |
452 | resultSlice, ok := result.([]any)
453 | if !ok {
454 | t.Fatalf("Expected result to be []any, got %T", result)
455 | }
456 |
457 | if len(resultSlice) != 2 {
458 | t.Errorf("Expected 2 results (Bob and Charlie), got %d", len(resultSlice))
459 | }
460 | })
461 |
462 | t.Run("EmptyResult", func(t *testing.T) {
463 | toolConfig := clickhousesql.Config{
464 | Name: "test-empty-result",
465 | Kind: "clickhouse-sql",
466 | Source: "test-clickhouse",
467 | Description: "Test query with no results",
468 | Statement: fmt.Sprintf("SELECT * FROM %s WHERE id = ?", tableName),
469 | Parameters: tools.Parameters{
470 | tools.NewIntParameter("id", "Record ID"),
471 | },
472 | }
473 |
474 | source := createMockSource(t, pool)
475 | sourcesMap := map[string]sources.Source{
476 | "test-clickhouse": source,
477 | }
478 |
479 | tool, err := toolConfig.Initialize(sourcesMap)
480 | if err != nil {
481 | t.Fatalf("Failed to initialize tool: %v", err)
482 | }
483 |
484 | params := tools.ParamValues{
485 | {Name: "id", Value: 999}, // Non-existent ID
486 | }
487 |
488 | result, err := tool.Invoke(ctx, params, "")
489 | if err != nil {
490 | t.Fatalf("Failed to invoke tool: %v", err)
491 | }
492 |
493 | // ClickHouse returns empty slice for no results, not nil
494 | if resultSlice, ok := result.([]any); ok {
495 | if len(resultSlice) != 0 {
496 | t.Errorf("Expected empty result for non-existent record, got %d results", len(resultSlice))
497 | }
498 | } else if result != nil {
499 | t.Errorf("Expected empty slice or nil result for empty query, got %v", result)
500 | }
501 | })
502 |
503 | t.Run("InvalidSQL", func(t *testing.T) {
504 | toolConfig := clickhousesql.Config{
505 | Name: "test-invalid-sql",
506 | Kind: "clickhouse-sql",
507 | Source: "test-clickhouse",
508 | Description: "Test invalid SQL",
509 | Statement: "SELEC * FROM nonexistent_table", // Typo in SELECT
510 | }
511 |
512 | source := createMockSource(t, pool)
513 | sourcesMap := map[string]sources.Source{
514 | "test-clickhouse": source,
515 | }
516 |
517 | tool, err := toolConfig.Initialize(sourcesMap)
518 | if err != nil {
519 | t.Fatalf("Failed to initialize tool: %v", err)
520 | }
521 |
522 | _, err = tool.Invoke(ctx, tools.ParamValues{}, "")
523 | if err == nil {
524 | t.Error("Expected error for invalid SQL, got nil")
525 | }
526 |
527 | if !strings.Contains(err.Error(), "Syntax error") && !strings.Contains(err.Error(), "SELEC") {
528 | t.Errorf("Expected syntax error message, got: %v", err)
529 | }
530 | })
531 |
532 | t.Logf("✅ clickhouse-sql tool tests completed successfully")
533 | }
534 |
535 | func TestClickHouseExecuteSQLTool(t *testing.T) {
536 | _ = getClickHouseVars(t)
537 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
538 | defer cancel()
539 |
540 | pool, err := initClickHouseConnectionPool(ClickHouseHost, ClickHousePort, ClickHouseUser, ClickHousePass, ClickHouseDatabase, ClickHouseProtocol)
541 | if err != nil {
542 | t.Fatalf("unable to create ClickHouse connection pool: %s", err)
543 | }
544 | defer pool.Close()
545 |
546 | tableName := "test_exec_sql_" + strings.ReplaceAll(uuid.New().String(), "-", "")
547 |
548 | t.Run("CreateTable", func(t *testing.T) {
549 | toolConfig := clickhouseexecutesql.Config{
550 | Name: "test-create-table",
551 | Kind: "clickhouse-execute-sql",
552 | Source: "test-clickhouse",
553 | Description: "Test create table",
554 | }
555 |
556 | source := createMockSource(t, pool)
557 | sourcesMap := map[string]sources.Source{
558 | "test-clickhouse": source,
559 | }
560 |
561 | tool, err := toolConfig.Initialize(sourcesMap)
562 | if err != nil {
563 | t.Fatalf("Failed to initialize tool: %v", err)
564 | }
565 |
566 | createSQL := fmt.Sprintf(`
567 | CREATE TABLE %s (
568 | id UInt32,
569 | data String
570 | ) ENGINE = Memory
571 | `, tableName)
572 |
573 | params := tools.ParamValues{
574 | {Name: "sql", Value: createSQL},
575 | }
576 |
577 | result, err := tool.Invoke(ctx, params, "")
578 | if err != nil {
579 | t.Fatalf("Failed to create table: %v", err)
580 | }
581 |
582 | // CREATE TABLE should return nil or empty slice (no rows)
583 | if resultSlice, ok := result.([]any); ok {
584 | if len(resultSlice) != 0 {
585 | t.Errorf("Expected empty result for CREATE TABLE, got %d results", len(resultSlice))
586 | }
587 | } else if result != nil {
588 | t.Errorf("Expected nil or empty slice for CREATE TABLE, got %v", result)
589 | }
590 | })
591 |
592 | t.Run("InsertData", func(t *testing.T) {
593 | toolConfig := clickhouseexecutesql.Config{
594 | Name: "test-insert",
595 | Kind: "clickhouse-execute-sql",
596 | Source: "test-clickhouse",
597 | Description: "Test insert data",
598 | }
599 |
600 | source := createMockSource(t, pool)
601 | sourcesMap := map[string]sources.Source{
602 | "test-clickhouse": source,
603 | }
604 |
605 | tool, err := toolConfig.Initialize(sourcesMap)
606 | if err != nil {
607 | t.Fatalf("Failed to initialize tool: %v", err)
608 | }
609 |
610 | insertSQL := fmt.Sprintf("INSERT INTO %s (id, data) VALUES (1, 'test1'), (2, 'test2')", tableName)
611 | params := tools.ParamValues{
612 | {Name: "sql", Value: insertSQL},
613 | }
614 |
615 | result, err := tool.Invoke(ctx, params, "")
616 | if err != nil {
617 | t.Fatalf("Failed to insert data: %v", err)
618 | }
619 |
620 | // INSERT should return nil or empty slice
621 | if resultSlice, ok := result.([]any); ok {
622 | if len(resultSlice) != 0 {
623 | t.Errorf("Expected empty result for INSERT, got %d results", len(resultSlice))
624 | }
625 | } else if result != nil {
626 | t.Errorf("Expected nil or empty slice for INSERT, got %v", result)
627 | }
628 | })
629 |
630 | t.Run("SelectData", func(t *testing.T) {
631 | toolConfig := clickhouseexecutesql.Config{
632 | Name: "test-select",
633 | Kind: "clickhouse-execute-sql",
634 | Source: "test-clickhouse",
635 | Description: "Test select data",
636 | }
637 |
638 | source := createMockSource(t, pool)
639 | sourcesMap := map[string]sources.Source{
640 | "test-clickhouse": source,
641 | }
642 |
643 | tool, err := toolConfig.Initialize(sourcesMap)
644 | if err != nil {
645 | t.Fatalf("Failed to initialize tool: %v", err)
646 | }
647 |
648 | selectSQL := fmt.Sprintf("SELECT * FROM %s ORDER BY id", tableName)
649 | params := tools.ParamValues{
650 | {Name: "sql", Value: selectSQL},
651 | }
652 |
653 | result, err := tool.Invoke(ctx, params, "")
654 | if err != nil {
655 | t.Fatalf("Failed to select data: %v", err)
656 | }
657 |
658 | resultSlice, ok := result.([]any)
659 | if !ok {
660 | t.Fatalf("Expected result to be []any, got %T", result)
661 | }
662 |
663 | if len(resultSlice) != 2 {
664 | t.Errorf("Expected 2 results, got %d", len(resultSlice))
665 | }
666 | })
667 |
668 | t.Run("DropTable", func(t *testing.T) {
669 | toolConfig := clickhouseexecutesql.Config{
670 | Name: "test-drop-table",
671 | Kind: "clickhouse-execute-sql",
672 | Source: "test-clickhouse",
673 | Description: "Test drop table",
674 | }
675 |
676 | source := createMockSource(t, pool)
677 | sourcesMap := map[string]sources.Source{
678 | "test-clickhouse": source,
679 | }
680 |
681 | tool, err := toolConfig.Initialize(sourcesMap)
682 | if err != nil {
683 | t.Fatalf("Failed to initialize tool: %v", err)
684 | }
685 |
686 | dropSQL := fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)
687 | params := tools.ParamValues{
688 | {Name: "sql", Value: dropSQL},
689 | }
690 |
691 | result, err := tool.Invoke(ctx, params, "")
692 | if err != nil {
693 | t.Fatalf("Failed to drop table: %v", err)
694 | }
695 |
696 | // DROP TABLE should return nil or empty slice
697 | if resultSlice, ok := result.([]any); ok {
698 | if len(resultSlice) != 0 {
699 | t.Errorf("Expected empty result for DROP TABLE, got %d results", len(resultSlice))
700 | }
701 | } else if result != nil {
702 | t.Errorf("Expected nil or empty slice for DROP TABLE, got %v", result)
703 | }
704 | })
705 |
706 | t.Run("MissingSQL", func(t *testing.T) {
707 | toolConfig := clickhouseexecutesql.Config{
708 | Name: "test-missing-sql",
709 | Kind: "clickhouse-execute-sql",
710 | Source: "test-clickhouse",
711 | Description: "Test missing SQL parameter",
712 | }
713 |
714 | source := createMockSource(t, pool)
715 | sourcesMap := map[string]sources.Source{
716 | "test-clickhouse": source,
717 | }
718 |
719 | tool, err := toolConfig.Initialize(sourcesMap)
720 | if err != nil {
721 | t.Fatalf("Failed to initialize tool: %v", err)
722 | }
723 |
724 | // Pass empty SQL parameter - this should cause an error
725 | params := tools.ParamValues{
726 | {Name: "sql", Value: ""},
727 | }
728 |
729 | _, err = tool.Invoke(ctx, params, "")
730 | if err == nil {
731 | t.Error("Expected error for empty SQL parameter, got nil")
732 | } else {
733 | t.Logf("Got expected error for empty SQL parameter: %v", err)
734 | }
735 | })
736 |
737 | t.Run("SQLInjectionAttempt", func(t *testing.T) {
738 | toolConfig := clickhouseexecutesql.Config{
739 | Name: "test-sql-injection",
740 | Kind: "clickhouse-execute-sql",
741 | Source: "test-clickhouse",
742 | Description: "Test SQL injection attempt",
743 | }
744 |
745 | source := createMockSource(t, pool)
746 | sourcesMap := map[string]sources.Source{
747 | "test-clickhouse": source,
748 | }
749 |
750 | tool, err := toolConfig.Initialize(sourcesMap)
751 | if err != nil {
752 | t.Fatalf("Failed to initialize tool: %v", err)
753 | }
754 |
755 | // Try to execute multiple statements (should fail or execute safely)
756 | injectionSQL := "SELECT 1; DROP TABLE system.users; SELECT 2"
757 | params := tools.ParamValues{
758 | {Name: "sql", Value: injectionSQL},
759 | }
760 |
761 | _, err = tool.Invoke(ctx, params, "")
762 | // This should either fail or only execute the first statement
763 | // dont check the specific error as behavior may vary
764 | _ = err // We're not checking the error intentionally
765 | })
766 |
767 | t.Logf("✅ clickhouse-execute-sql tool tests completed successfully")
768 | }
769 |
770 | func TestClickHouseEdgeCases(t *testing.T) {
771 | _ = getClickHouseVars(t)
772 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
773 | defer cancel()
774 |
775 | pool, err := initClickHouseConnectionPool(ClickHouseHost, ClickHousePort, ClickHouseUser, ClickHousePass, ClickHouseDatabase, ClickHouseProtocol)
776 | if err != nil {
777 | t.Fatalf("unable to create ClickHouse connection pool: %s", err)
778 | }
779 | defer pool.Close()
780 |
781 | t.Run("VeryLongQuery", func(t *testing.T) {
782 | // Create a very long but valid query
783 | var conditions []string
784 | for i := 1; i <= 100; i++ {
785 | conditions = append(conditions, fmt.Sprintf("(%d = %d)", i, i))
786 | }
787 | longQuery := "SELECT 1 WHERE " + strings.Join(conditions, " AND ")
788 |
789 | toolConfig := clickhouseexecutesql.Config{
790 | Name: "test-long-query",
791 | Kind: "clickhouse-execute-sql",
792 | Source: "test-clickhouse",
793 | Description: "Test very long query",
794 | }
795 |
796 | source := createMockSource(t, pool)
797 | sourcesMap := map[string]sources.Source{
798 | "test-clickhouse": source,
799 | }
800 |
801 | tool, err := toolConfig.Initialize(sourcesMap)
802 | if err != nil {
803 | t.Fatalf("Failed to initialize tool: %v", err)
804 | }
805 |
806 | params := tools.ParamValues{
807 | {Name: "sql", Value: longQuery},
808 | }
809 |
810 | result, err := tool.Invoke(ctx, params, "")
811 | if err != nil {
812 | t.Fatalf("Failed to execute long query: %v", err)
813 | }
814 |
815 | // Should return [{1:1}]
816 | if resultSlice, ok := result.([]any); ok {
817 | if len(resultSlice) != 1 {
818 | t.Errorf("Expected 1 result from long query, got %d", len(resultSlice))
819 | }
820 | }
821 | })
822 |
823 | t.Run("NullValues", func(t *testing.T) {
824 | tableName := "test_nulls_" + strings.ReplaceAll(uuid.New().String(), "-", "")
825 | createSQL := fmt.Sprintf(`
826 | CREATE TABLE %s (
827 | id UInt32,
828 | nullable_field Nullable(String)
829 | ) ENGINE = Memory
830 | `, tableName)
831 |
832 | _, err = pool.ExecContext(ctx, createSQL)
833 | if err != nil {
834 | t.Fatalf("Failed to create table: %v", err)
835 | }
836 | defer func() {
837 | _, _ = pool.ExecContext(ctx, fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName))
838 | }()
839 |
840 | // Insert null value
841 | insertSQL := fmt.Sprintf("INSERT INTO %s (id, nullable_field) VALUES (1, NULL), (2, 'not null')", tableName)
842 | _, err = pool.ExecContext(ctx, insertSQL)
843 | if err != nil {
844 | t.Fatalf("Failed to insert null value: %v", err)
845 | }
846 |
847 | toolConfig := clickhousesql.Config{
848 | Name: "test-null-values",
849 | Kind: "clickhouse-sql",
850 | Source: "test-clickhouse",
851 | Description: "Test null values",
852 | Statement: fmt.Sprintf("SELECT * FROM %s ORDER BY id", tableName),
853 | }
854 |
855 | source := createMockSource(t, pool)
856 | sourcesMap := map[string]sources.Source{
857 | "test-clickhouse": source,
858 | }
859 |
860 | tool, err := toolConfig.Initialize(sourcesMap)
861 | if err != nil {
862 | t.Fatalf("Failed to initialize tool: %v", err)
863 | }
864 |
865 | result, err := tool.Invoke(ctx, tools.ParamValues{}, "")
866 | if err != nil {
867 | t.Fatalf("Failed to select null values: %v", err)
868 | }
869 |
870 | resultSlice, ok := result.([]any)
871 | if !ok {
872 | t.Fatalf("Expected result to be []any, got %T", result)
873 | }
874 |
875 | if len(resultSlice) != 2 {
876 | t.Errorf("Expected 2 results, got %d", len(resultSlice))
877 | }
878 |
879 | // Check that null is properly handled
880 | if firstRow, ok := resultSlice[0].(map[string]any); ok {
881 | if _, hasNullableField := firstRow["nullable_field"]; !hasNullableField {
882 | t.Error("Expected nullable_field in result")
883 | }
884 | }
885 | })
886 |
887 | t.Run("ConcurrentQueries", func(t *testing.T) {
888 | toolConfig := clickhousesql.Config{
889 | Name: "test-concurrent",
890 | Kind: "clickhouse-sql",
891 | Source: "test-clickhouse",
892 | Description: "Test concurrent queries",
893 | Statement: "SELECT number FROM system.numbers LIMIT ?",
894 | Parameters: tools.Parameters{
895 | tools.NewIntParameter("limit", "Limit"),
896 | },
897 | }
898 |
899 | source := createMockSource(t, pool)
900 | sourcesMap := map[string]sources.Source{
901 | "test-clickhouse": source,
902 | }
903 |
904 | tool, err := toolConfig.Initialize(sourcesMap)
905 | if err != nil {
906 | t.Fatalf("Failed to initialize tool: %v", err)
907 | }
908 |
909 | // Run multiple queries concurrently
910 | done := make(chan bool, 5)
911 | for i := 0; i < 5; i++ {
912 | go func(n int) {
913 | defer func() { done <- true }()
914 |
915 | params := tools.ParamValues{
916 | {Name: "limit", Value: n + 1},
917 | }
918 |
919 | result, err := tool.Invoke(ctx, params, "")
920 | if err != nil {
921 | t.Errorf("Concurrent query %d failed: %v", n, err)
922 | return
923 | }
924 |
925 | if resultSlice, ok := result.([]any); ok {
926 | if len(resultSlice) != n+1 {
927 | t.Errorf("Query %d: expected %d results, got %d", n, n+1, len(resultSlice))
928 | }
929 | }
930 | }(i)
931 | }
932 |
933 | // Wait for all goroutines
934 | for i := 0; i < 5; i++ {
935 | <-done
936 | }
937 | })
938 |
939 | t.Logf("✅ Edge case tests completed successfully")
940 | }
941 |
942 | func createMockSource(t *testing.T, pool *sql.DB) sources.Source {
943 | config := clickhouse.Config{
944 | Host: ClickHouseHost,
945 | Port: ClickHousePort,
946 | Database: ClickHouseDatabase,
947 | User: ClickHouseUser,
948 | Password: ClickHousePass,
949 | Protocol: ClickHouseProtocol,
950 | Secure: false,
951 | }
952 |
953 | source, err := config.Initialize(context.Background(), noop.NewTracerProvider().Tracer(""))
954 | if err != nil {
955 | t.Fatalf("Failed to initialize source: %v", err)
956 | }
957 |
958 | return source
959 | }
960 |
961 | // getClickHouseSQLParamToolInfo returns statements and param for my-tool clickhouse-sql kind
962 | func getClickHouseSQLParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
963 | createStatement := fmt.Sprintf("CREATE TABLE %s (id UInt32, name String) ENGINE = Memory", tableName)
964 | insertStatement := fmt.Sprintf("INSERT INTO %s (id, name) VALUES (?, ?), (?, ?), (?, ?), (?, ?)", tableName)
965 | paramStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ? OR name = ?", tableName)
966 | idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ?", tableName)
967 | nameParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE name = ?", tableName)
968 | arrayStatement := fmt.Sprintf("SELECT * FROM %s WHERE id IN (?) AND name IN (?)", tableName)
969 | params := []any{1, "Alice", 2, "Bob", 3, "Sid", 4, nil}
970 | return createStatement, insertStatement, paramStatement, idParamStatement, nameParamStatement, arrayStatement, params
971 | }
972 |
973 | // getClickHouseSQLAuthToolInfo returns statements and param of my-auth-tool for clickhouse-sql kind
974 | func getClickHouseSQLAuthToolInfo(tableName string) (string, string, string, []any) {
975 | createStatement := fmt.Sprintf("CREATE TABLE %s (id UInt32, name String, email String) ENGINE = Memory", tableName)
976 | insertStatement := fmt.Sprintf("INSERT INTO %s (id, name, email) VALUES (?, ?, ?), (?, ?, ?)", tableName)
977 | authStatement := fmt.Sprintf("SELECT name FROM %s WHERE email = ?", tableName)
978 | params := []any{1, "Alice", tests.ServiceAccountEmail, 2, "jane", "[email protected]"}
979 | return createStatement, insertStatement, authStatement, params
980 | }
981 |
982 | // getClickHouseSQLTmplToolStatement returns statements and param for template parameter test cases for clickhouse-sql kind
983 | func getClickHouseSQLTmplToolStatement() (string, string) {
984 | tmplSelectCombined := "SELECT * FROM {{.tableName}} WHERE id = ?"
985 | tmplSelectFilterCombined := "SELECT * FROM {{.tableName}} WHERE {{.columnFilter}} = ?"
986 | return tmplSelectCombined, tmplSelectFilterCombined
987 | }
988 |
989 | // SetupClickHouseSQLTable creates and inserts data into a table of tool
990 | // compatible with clickhouse-sql tool
991 | func setupClickHouseSQLTable(t *testing.T, ctx context.Context, pool *sql.DB, createStatement, insertStatement, tableName string, params []any) func(*testing.T) {
992 | err := pool.PingContext(ctx)
993 | if err != nil {
994 | t.Fatalf("unable to connect to test database: %s", err)
995 | }
996 |
997 | // Create table
998 | _, err = pool.ExecContext(ctx, createStatement)
999 | if err != nil {
1000 | t.Fatalf("unable to create test table %s: %s", tableName, err)
1001 | }
1002 |
1003 | // Insert test data
1004 | _, err = pool.ExecContext(ctx, insertStatement, params...)
1005 | if err != nil {
1006 | t.Fatalf("unable to insert test data: %s", err)
1007 | }
1008 |
1009 | return func(t *testing.T) {
1010 | // tear down test
1011 | _, err = pool.ExecContext(ctx, fmt.Sprintf("DROP TABLE %s", tableName))
1012 | if err != nil {
1013 | t.Errorf("Teardown failed: %s", err)
1014 | }
1015 | }
1016 | }
1017 |
1018 | func TestClickHouseListDatabasesTool(t *testing.T) {
1019 | _ = getClickHouseVars(t)
1020 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
1021 | defer cancel()
1022 |
1023 | pool, err := initClickHouseConnectionPool(ClickHouseHost, ClickHousePort, ClickHouseUser, ClickHousePass, ClickHouseDatabase, ClickHouseProtocol)
1024 | if err != nil {
1025 | t.Fatalf("unable to create ClickHouse connection pool: %s", err)
1026 | }
1027 | defer pool.Close()
1028 |
1029 | // Create a test database
1030 | testDBName := "test_list_db_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
1031 | _, err = pool.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", testDBName))
1032 | if err != nil {
1033 | t.Fatalf("Failed to create test database: %v", err)
1034 | }
1035 | defer func() {
1036 | _, _ = pool.ExecContext(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS %s", testDBName))
1037 | }()
1038 |
1039 | t.Run("ListDatabases", func(t *testing.T) {
1040 | toolConfig := clickhouselistdatabases.Config{
1041 | Name: "test-list-databases",
1042 | Kind: "clickhouse-list-databases",
1043 | Source: "test-clickhouse",
1044 | Description: "Test listing databases",
1045 | }
1046 |
1047 | source := createMockSource(t, pool)
1048 | sourcesMap := map[string]sources.Source{
1049 | "test-clickhouse": source,
1050 | }
1051 |
1052 | tool, err := toolConfig.Initialize(sourcesMap)
1053 | if err != nil {
1054 | t.Fatalf("Failed to initialize tool: %v", err)
1055 | }
1056 |
1057 | params := tools.ParamValues{}
1058 |
1059 | result, err := tool.Invoke(ctx, params, "")
1060 | if err != nil {
1061 | t.Fatalf("Failed to list databases: %v", err)
1062 | }
1063 |
1064 | databases, ok := result.([]map[string]any)
1065 | if !ok {
1066 | t.Fatalf("Expected result to be []map[string]any, got %T", result)
1067 | }
1068 |
1069 | // Should contain at least the default database and our test database - system and default
1070 | if len(databases) < 2 {
1071 | t.Errorf("Expected at least 2 databases, got %d", len(databases))
1072 | }
1073 |
1074 | found := false
1075 | foundDefault := false
1076 | for _, db := range databases {
1077 | if name, ok := db["name"].(string); ok {
1078 | if name == testDBName {
1079 | found = true
1080 | }
1081 | if name == "default" || name == "system" {
1082 | foundDefault = true
1083 | }
1084 | }
1085 | }
1086 |
1087 | if !found {
1088 | t.Errorf("Test database %s not found in list", testDBName)
1089 | }
1090 | if !foundDefault {
1091 | t.Errorf("Default/system database not found in list")
1092 | }
1093 |
1094 | t.Logf("Successfully listed %d databases", len(databases))
1095 | })
1096 |
1097 | t.Run("ListDatabasesWithInvalidSource", func(t *testing.T) {
1098 | toolConfig := clickhouselistdatabases.Config{
1099 | Name: "test-invalid-source",
1100 | Kind: "clickhouse-list-databases",
1101 | Source: "non-existent-source",
1102 | Description: "Test with invalid source",
1103 | }
1104 |
1105 | sourcesMap := map[string]sources.Source{}
1106 |
1107 | _, err := toolConfig.Initialize(sourcesMap)
1108 | if err == nil {
1109 | t.Error("Expected error for non-existent source, got nil")
1110 | } else {
1111 | t.Logf("Got expected error for invalid source: %v", err)
1112 | }
1113 | })
1114 |
1115 | t.Logf("✅ clickhouse-list-databases tool tests completed successfully")
1116 | }
1117 |
1118 | func TestClickHouseListTablesTool(t *testing.T) {
1119 | _ = getClickHouseVars(t)
1120 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
1121 | defer cancel()
1122 |
1123 | pool, err := initClickHouseConnectionPool(ClickHouseHost, ClickHousePort, ClickHouseUser, ClickHousePass, ClickHouseDatabase, ClickHouseProtocol)
1124 | if err != nil {
1125 | t.Fatalf("unable to create ClickHouse connection pool: %s", err)
1126 | }
1127 | defer pool.Close()
1128 |
1129 | // Create a test database with tables
1130 | testDBName := "test_list_tables_db_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
1131 | _, err = pool.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", testDBName))
1132 | if err != nil {
1133 | t.Fatalf("Failed to create test database: %v", err)
1134 | }
1135 | defer func() {
1136 | _, _ = pool.ExecContext(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS %s", testDBName))
1137 | }()
1138 |
1139 | // Create test tables in the test database
1140 | testTable1 := "test_table_1"
1141 | testTable2 := "test_table_2"
1142 | _, err = pool.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s.%s (id UInt32, name String) ENGINE = Memory", testDBName, testTable1))
1143 | if err != nil {
1144 | t.Fatalf("Failed to create test table 1: %v", err)
1145 | }
1146 | _, err = pool.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s.%s (id UInt32, value Float64) ENGINE = Memory", testDBName, testTable2))
1147 | if err != nil {
1148 | t.Fatalf("Failed to create test table 2: %v", err)
1149 | }
1150 |
1151 | t.Run("ListTables", func(t *testing.T) {
1152 | toolConfig := clickhouselisttables.Config{
1153 | Name: "test-list-tables",
1154 | Kind: "clickhouse-list-tables",
1155 | Source: "test-clickhouse",
1156 | Description: "Test listing tables",
1157 | }
1158 |
1159 | source := createMockSource(t, pool)
1160 | sourcesMap := map[string]sources.Source{
1161 | "test-clickhouse": source,
1162 | }
1163 |
1164 | tool, err := toolConfig.Initialize(sourcesMap)
1165 | if err != nil {
1166 | t.Fatalf("Failed to initialize tool: %v", err)
1167 | }
1168 |
1169 | params := tools.ParamValues{
1170 | {Name: "database", Value: testDBName},
1171 | }
1172 |
1173 | result, err := tool.Invoke(ctx, params, "")
1174 | if err != nil {
1175 | t.Fatalf("Failed to list tables: %v", err)
1176 | }
1177 |
1178 | tables, ok := result.([]map[string]any)
1179 | if !ok {
1180 | t.Fatalf("Expected result to be []map[string]any, got %T", result)
1181 | }
1182 |
1183 | // Should contain exactly 2 tables that we created
1184 | if len(tables) != 2 {
1185 | t.Errorf("Expected 2 tables, got %d", len(tables))
1186 | }
1187 |
1188 | foundTable1 := false
1189 | foundTable2 := false
1190 | for _, table := range tables {
1191 | if name, ok := table["name"].(string); ok {
1192 | if name == testTable1 {
1193 | foundTable1 = true
1194 | }
1195 | if name == testTable2 {
1196 | foundTable2 = true
1197 | }
1198 | // Verify database field is set correctly
1199 | if db, ok := table["database"].(string); ok {
1200 | if db != testDBName {
1201 | t.Errorf("Expected database to be %s, got %s", testDBName, db)
1202 | }
1203 | }
1204 | }
1205 | }
1206 |
1207 | if !foundTable1 {
1208 | t.Errorf("Test table %s not found in list", testTable1)
1209 | }
1210 | if !foundTable2 {
1211 | t.Errorf("Test table %s not found in list", testTable2)
1212 | }
1213 |
1214 | t.Logf("Successfully listed %d tables from database %s", len(tables), testDBName)
1215 | })
1216 |
1217 | t.Run("ListTablesWithMissingDatabase", func(t *testing.T) {
1218 | toolConfig := clickhouselisttables.Config{
1219 | Name: "test-list-tables-missing-db",
1220 | Kind: "clickhouse-list-tables",
1221 | Source: "test-clickhouse",
1222 | Description: "Test listing tables without database parameter",
1223 | }
1224 |
1225 | source := createMockSource(t, pool)
1226 | sourcesMap := map[string]sources.Source{
1227 | "test-clickhouse": source,
1228 | }
1229 |
1230 | tool, err := toolConfig.Initialize(sourcesMap)
1231 | if err != nil {
1232 | t.Fatalf("Failed to initialize tool: %v", err)
1233 | }
1234 |
1235 | params := tools.ParamValues{}
1236 |
1237 | _, err = tool.Invoke(ctx, params, "")
1238 | if err == nil {
1239 | t.Error("Expected error for missing database parameter, got nil")
1240 | } else {
1241 | t.Logf("Got expected error for missing database: %v", err)
1242 | }
1243 | })
1244 |
1245 | t.Run("ListTablesWithInvalidSource", func(t *testing.T) {
1246 | toolConfig := clickhouselisttables.Config{
1247 | Name: "test-invalid-source",
1248 | Kind: "clickhouse-list-tables",
1249 | Source: "non-existent-source",
1250 | Description: "Test with invalid source",
1251 | }
1252 |
1253 | sourcesMap := map[string]sources.Source{}
1254 |
1255 | _, err := toolConfig.Initialize(sourcesMap)
1256 | if err == nil {
1257 | t.Error("Expected error for non-existent source, got nil")
1258 | } else {
1259 | t.Logf("Got expected error for invalid source: %v", err)
1260 | }
1261 | })
1262 |
1263 | t.Logf("✅ clickhouse-list-tables tool tests completed successfully")
1264 | }
1265 |
```