Database Query Skill
Overview
This skill enables querying Spanner databases through the Automizely DBP API. It uses the go-admin-automizely-cli library to obtain authentication tokens and execute SQL queries against Spanner databases in different environments.
Main Production Instances:
p-connectors-usce1 - Connectors instance
aftership-pro-1 - Main AfterShip instance
The script automatically retrieves the database_id by querying the database list API before executing queries.
Production Databases:
| Instance |
Database |
Tables |
aftership-pro-1 |
af-p-core |
combinable_orders, competitor_migrate_records, competitor_synced_orders, couriers, crons, events, feature_canary_rules, feature_status, feed_fulfillment_orders, feed_order_items, feed_orders, feed_returns, feeds, fulfillment_order_routings, hub_orders, hub_returns, order_actions, order_routings, reconciliations, return_routings, settings, tasks, web_storages |
p-connectors-usce1 |
connectors-p-core |
action_execution_records, amazon_sp_jobs_20250115094545, app_connections, app_events, app_platforms, blog_posts, blog_tags, blogs, carrier_services, category_rules, connection_associations, count_stats, coupons, credentials, cron_tasks, custom_warehouses, discounts, discounts_codes, error_codes, event_notifications, events, exchange_rates, exchange_rates_latest, fulfillment_services, gdpr_requests, gift_cards, idempotent_requests, image_upload_records, kv_config, merchant_configs, metafield_definitions, metafields, pages, partner_connections, partners, price_rules, product_categories, publications, sales_channels, scheduler_workflow_instances, scheduler_workflows, scripts, sessions, state_pipelines, storefront_access_tokens, stores, tasks, theme_assets, themes, unauthorized_tasks, warehouses, weaver_rules, webpixels |
p-connectors-usce1 |
connectors-p-order |
checkouts, draft_orders, fulfillment_orders, order_cancellations, order_fulfillments, order_refunds, order_restocks, order_return_calculations, order_tracking_events, order_transactions, orders, orders_items, orders_trackings, payment_refunds, payments, returns, warehouse_returns |
p-connectors-usce1 |
products-p-listings |
organization_settings, product_listing_audit_versions, product_listing_relations, product_listings, settings |
p-connectors-usce1 |
products-p-core |
bundled_listing_variant_relations, bundled_listings, collection_product_relations, collections, combined_listing_product_relations, combined_listings, products |
p-connectors-usce1 |
connectors-p-jobs |
jobs, job_groups |
When to Use This Skill
Use this skill when Billy needs to:
- Query Spanner database records
- Investigate data issues or verify data states
- Fetch specific records for debugging or analysis
- Profile query performance
Prerequisites
Implementation Steps
Step 1: Install and Setup
First, install the go-admin-automizely-cli library:
go get -u github.com/AfterShip/go-admin-automizely-cli
Step 2: Get Token
Create a Go script that uses the client.GetToken method to obtain an authentication token:
package main
import (
"context"
"fmt"
"log"
"github.com/AfterShip/go-admin-automizely-cli/client"
)
func main() {
// Use "testing" for test environment or "production" for production
token, err := client.GetToken(context.Background(), "production")
if err != nil {
log.Fatalf("Failed to get token: %v", err)
}
fmt.Println(token)
}
Environment Options:
"testing" - For test environment (aftership-test)
"production" - For production environment (aftership-pro)
Step 2: Get Database ID (if needed)
If you don't know the database_id, first query the database list API to get it:
API Endpoint:
https://api.automizely.org/dbp/v2/instances/${instance_name}/databases?database_name=${database_name}&db_type=spanner&gcp_project=${gcp_project}&instance=${instance_name}&limit=20&page=1
Common Production Instances:
p-connectors-usce1 - Connectors instance
aftership-pro-1 - Main AfterShip instance
Example Response:
{
"meta": {
"code": 20000,
"type": "OK",
"message": "The request was successfully processed by AfterShip."
},
"data": {
"databases": [
{
"instance_name": "aftership-pro-1",
"database_name": "af-p-core",
"database_id": 170,
"gcp_project": "aftership-pro",
"env": "production",
"product_id": 98,
"product_name": "AfterShip Feed",
"backend_owner": "xq.yan@aftership.com",
"db_type": "spanner",
"modules": ["Feed Internal", "Automizely Feed"]
}
],
"pagination": {
"total": 1,
"page": 1,
"next_cursor": null,
"limit": 20,
"has_next_page": false
}
}
}
Extract the database_id from data.databases[0].database_id.
Step 3: Execute Query Against DBP API
Once you have the token and database_id, use them to query the database through the API:
API Endpoint:
https://api.automizely.org/dbp/v2/instances/${instance_name}/databases/${database_id}/query-result
Request Format:
{
"db_type": "spanner",
"gcp_project": "aftership-test", // or "aftership-pro" for production
"query": "SELECT * FROM orders WHERE order_id='xxx' LIMIT 10;",
"query_mode": "profile"
}
Step 4: Complete Go Script
Here's a complete Go script that handles token retrieval, database_id lookup, and database querying:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/AfterShip/go-admin-automizely-cli/client"
)
type QueryRequest struct {
DBType string `json:"db_type"`
GCPProject string `json:"gcp_project"`
Query string `json:"query"`
QueryMode string `json:"query_mode"`
}
type DatabaseInfo struct {
InstanceName string `json:"instance_name"`
DatabaseName string `json:"database_name"`
DatabaseID int `json:"database_id"`
GCPProject string `json:"gcp_project"`
Env string `json:"env"`
}
type DatabaseListResponse struct {
Meta struct {
Code int `json:"code"`
Type string `json:"type"`
Message string `json:"message"`
} `json:"meta"`
Data struct {
Databases []DatabaseInfo `json:"databases"`
} `json:"data"`
}
func main() {
if len(os.Args) < 5 {
log.Fatal("Usage: go run script.go <instance_name> <database_name> <environment> <sql_query>\n" +
" instance_name: p-connectors-usce1, aftership-pro-1, etc.\n" +
" database_name: af-p-core, af-p-feed, etc.\n" +
" environment: testing or production\n" +
" sql_query: SQL query with LIMIT (max 1000)")
}
instanceName := os.Args[1]
databaseName := os.Args[2]
environment := os.Args[3] // "testing" or "production"
sqlQuery := os.Args[4]
// Get authentication token using client.GetToken
token, err := client.GetToken(context.Background(), environment)
if err != nil {
log.Fatalf("Failed to get token: %v", err)
}
// Determine GCP project based on environment
gcpProject := "aftership-test"
if environment == "production" {
gcpProject = "aftership-pro"
}
// Step 1: Get database_id by querying database list
databaseID, err := getDatabaseID(token, instanceName, databaseName, gcpProject)
if err != nil {
log.Fatalf("Failed to get database ID: %v", err)
}
fmt.Printf("Found database_id: %d for %s/%s\n", databaseID, instanceName, databaseName)
// Step 2: Execute query
result, err := executeQuery(token, instanceName, databaseID, gcpProject, sqlQuery)
if err != nil {
log.Fatalf("Failed to execute query: %v", err)
}
fmt.Println("\nQuery Result:")
fmt.Println(result)
}
func getDatabaseID(token, instanceName, databaseName, gcpProject string) (int, error) {
url := fmt.Sprintf("https://api.automizely.org/dbp/v2/instances/%s/databases?database_name=%s&db_type=spanner&gcp_project=%s&instance=%s&limit=20&page=1",
instanceName, databaseName, gcpProject, instanceName)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return 0, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return 0, fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return 0, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
var dbResponse DatabaseListResponse
if err := json.Unmarshal(body, &dbResponse); err != nil {
return 0, fmt.Errorf("failed to unmarshal response: %w", err)
}
if len(dbResponse.Data.Databases) == 0 {
return 0, fmt.Errorf("no database found with name %s in instance %s", databaseName, instanceName)
}
return dbResponse.Data.Databases[0].DatabaseID, nil
}
func executeQuery(token, instanceName string, databaseID int, gcpProject, sqlQuery string) (string, error) {
// Prepare request
reqBody := QueryRequest{
DBType: "spanner",
GCPProject: gcpProject,
Query: sqlQuery,
QueryMode: "profile",
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("failed to marshal request: %w", err)
}
// Make API request
url := fmt.Sprintf("https://api.automizely.org/dbp/v2/instances/%s/databases/%d/query-result", instanceName, databaseID)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
// Read response
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
return string(body), nil
}
Important Constraints
Instance and Database Parameters
- instance_name: The Spanner instance name (e.g.,
p-connectors-usce1, aftership-pro-1)
- database_name: The logical database name (e.g.,
af-p-core, af-p-feed)
- database_id: Automatically retrieved from the database list API
- The script first queries the database list API to get the database_id, then uses it to execute the query
- DDL: When you do not know the DDL of a table, write a go script to call API
https://api.automizely.org/dbp/v2/instances/${instance_name}/databases/${database_id}/tables/${table_name}/ddl?db_type=spanner&gcp_project=aftership-pro&mode=Schemas&table_name=${table_name}. And you can get the ddl from the response.
response example:
{
"meta": {
"code": 20000,
"type": "OK",
"message": "The request was successfully processed by AfterShip."
},
"data": "CREATE TABLE job_groups (\n group_id STRING(32) NOT NULL,\n namespace STRING(256) NOT NULL,\n project STRING(256) NOT NULL,\n name STRING(256) NOT NULL,\n job_topic_name STRING(256) NOT NULL,\n notification_topic_name STRING(256),\n concurrency INT64,\n retry_config STRING(MAX) NOT NULL,\n created_at TIMESTAMP NOT NULL OPTIONS (\n allow_commit_timestamp = true\n ),\n updated_at TIMESTAMP NOT NULL OPTIONS (\n allow_commit_timestamp = true\n ),\n) PRIMARY KEY(group_id)"
}
Query Requirements
- LIMIT is MANDATORY: Every SQL query MUST include a LIMIT clause
- Maximum LIMIT: The LIMIT value cannot exceed 1000
- Query Validation: Always validate that the query includes LIMIT before execution
Example valid queries:
SELECT * FROM orders WHERE order_id='xxx' LIMIT 10;
SELECT order_id, status FROM orders WHERE created_at > '2024-01-01' LIMIT 100;
SELECT COUNT(*) as count FROM orders LIMIT 1;
Environment Configuration
- Test Environment: Use environment
"testing" with gcp_project: "aftership-test"
- Production Environment: Use environment
"production" with gcp_project: "aftership-pro"
Query Modes
- profile: Returns query results with execution statistics and performance metrics
- Use "profile" mode by default for better debugging insights
Usage Examples
Example 1: Query Single Order from Feed Database (Production)
go run query_db.go "aftership-pro-1" "af-p-core" "production" "SELECT * FROM orders WHERE order_id='6f851c942e604330b7165aa2408047d3' LIMIT 10;"
Example 2: Query from Connectors Instance (Production)
go run query_db.go "p-connectors-usce1" "af-p-connectors" "production" "SELECT order_id, status, created_at FROM orders WHERE created_at > TIMESTAMP('2024-11-01') ORDER BY created_at DESC LIMIT 100;"
Example 3: Count Orders by Status (Test Environment)
go run query_db.go "aftership-test-1" "af-t-core" "testing" "SELECT status, COUNT(*) as count FROM orders GROUP BY status LIMIT 1000;"
Example 4: Check Inventory Records
go run query_db.go "aftership-pro-1" "af-p-core" "production" "SELECT * FROM inventory WHERE sku='ABC123' LIMIT 50;"
Error Handling
Common errors and solutions:
- Authentication Failed: Ensure go-admin-automizely-cli is properly configured
- Database Not Found: Verify the instance_name and database_name are correct
- Missing LIMIT: Add LIMIT clause to your SQL query (required)
- LIMIT Too Large: Reduce LIMIT to 1000 or less
- Invalid Instance Name: Use correct instance names (p-connectors-usce1, aftership-pro-1, etc.)
- Query Timeout: Optimize query or reduce LIMIT value
- Empty Database List: Check if the database exists in the specified instance
Best Practices
- Always start with a small LIMIT (e.g., 10) for exploratory queries
- Use specific WHERE clauses to reduce query scope
- Use indexes when available for better performance
- Review query execution statistics from profile mode
- Test queries in test environment before running in production
- Keep sensitive data secure - don't log tokens or credentials
Response Format
The API returns JSON with the following structure:
- Query results as an array of records
- Execution statistics (when using profile mode)
- Column metadata
- Row count information
Notes
- This tool is primarily for Spanner databases but the structure can be adapted for other database types
- Always respect data privacy and security policies when querying production data
- Use appropriate LIMIT values to avoid performance impact on production systems
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: db-query-23description: This skill enables querying Spanner databases through the AfterShip DSP API. It uses the go-admin-automizely-cli library to obtain authentication tokens and execute SQL queries against Spanner databases in different environments. Use when this capability is needed.4---56# Database Query Skill78## Overview9This skill enables querying Spanner databases through the Automizely DBP API. It uses the go-admin-automizely-cli library to obtain authentication tokens and execute SQL queries against Spanner databases in different environments.1011**Main Production Instances:**12- `p-connectors-usce1` - Connectors instance13- `aftership-pro-1` - Main AfterShip instance1415The script automatically retrieves the database_id by querying the database list API before executing queries.1617**Production Databases:**1819| Instance | Database | Tables |20| :--- | :--- | :--- |21| `aftership-pro-1` | `af-p-core` | `combinable_orders`, `competitor_migrate_records`, `competitor_synced_orders`, `couriers`, `crons`, `events`, `feature_canary_rules`, `feature_status`, `feed_fulfillment_orders`, `feed_order_items`, `feed_orders`, `feed_returns`, `feeds`, `fulfillment_order_routings`, `hub_orders`, `hub_returns`, `order_actions`, `order_routings`, `reconciliations`, `return_routings`, `settings`, `tasks`, `web_storages` |22| `p-connectors-usce1` | `connectors-p-core` | `action_execution_records`, `amazon_sp_jobs_20250115094545`, `app_connections`, `app_events`, `app_platforms`, `blog_posts`, `blog_tags`, `blogs`, `carrier_services`, `category_rules`, `connection_associations`, `count_stats`, `coupons`, `credentials`, `cron_tasks`, `custom_warehouses`, `discounts`, `discounts_codes`, `error_codes`, `event_notifications`, `events`, `exchange_rates`, `exchange_rates_latest`, `fulfillment_services`, `gdpr_requests`, `gift_cards`, `idempotent_requests`, `image_upload_records`, `kv_config`, `merchant_configs`, `metafield_definitions`, `metafields`, `pages`, `partner_connections`, `partners`, `price_rules`, `product_categories`, `publications`, `sales_channels`, `scheduler_workflow_instances`, `scheduler_workflows`, `scripts`, `sessions`, `state_pipelines`, `storefront_access_tokens`, `stores`, `tasks`, `theme_assets`, `themes`, `unauthorized_tasks`, `warehouses`, `weaver_rules`, `webpixels` |23| `p-connectors-usce1` | `connectors-p-order` | `checkouts`, `draft_orders`, `fulfillment_orders`, `order_cancellations`, `order_fulfillments`, `order_refunds`, `order_restocks`, `order_return_calculations`, `order_tracking_events`, `order_transactions`, `orders`, `orders_items`, `orders_trackings`, `payment_refunds`, `payments`, `returns`, `warehouse_returns` |24| `p-connectors-usce1` | `products-p-listings` | `organization_settings`, `product_listing_audit_versions`, `product_listing_relations`, `product_listings`, `settings` |25| `p-connectors-usce1` | `products-p-core` | `bundled_listing_variant_relations`, `bundled_listings`, `collection_product_relations`, `collections`, `combined_listing_product_relations`, `combined_listings`, `products` |26| `p-connectors-usce1` | `connectors-p-jobs` | `jobs`, `job_groups` |27282930## When to Use This Skill31Use this skill when Billy needs to:32- Query Spanner database records33- Investigate data issues or verify data states34- Fetch specific records for debugging or analysis35- Profile query performance3637## Prerequisites38- Go environment set up39- Access to https://github.com/AfterShip/go-admin-automizely-cli library40- Appropriate permissions to access the databases4142## Implementation Steps4344### Step 1: Install and Setup45First, install the go-admin-automizely-cli library:4647```bash48go get -u github.com/AfterShip/go-admin-automizely-cli49```5051### Step 2: Get Token52Create a Go script that uses the client.GetToken method to obtain an authentication token:5354```go55package main5657import (58 "context"59 "fmt"60 "log"61 62 "github.com/AfterShip/go-admin-automizely-cli/client"63)6465func main() {66 // Use "testing" for test environment or "production" for production67 token, err := client.GetToken(context.Background(), "production")68 if err != nil {69 log.Fatalf("Failed to get token: %v", err)70 }71 fmt.Println(token)72}73```7475**Environment Options:**76- `"testing"` - For test environment (aftership-test)77- `"production"` - For production environment (aftership-pro)7879### Step 2: Get Database ID (if needed)80If you don't know the database_id, first query the database list API to get it:8182**API Endpoint:**83```84https://api.automizely.org/dbp/v2/instances/${instance_name}/databases?database_name=${database_name}&db_type=spanner&gcp_project=${gcp_project}&instance=${instance_name}&limit=20&page=185```8687**Common Production Instances:**88- `p-connectors-usce1` - Connectors instance89- `aftership-pro-1` - Main AfterShip instance9091**Example Response:**92```json93{94 "meta": {95 "code": 20000,96 "type": "OK",97 "message": "The request was successfully processed by AfterShip."98 },99 "data": {100 "databases": [101 {102 "instance_name": "aftership-pro-1",103 "database_name": "af-p-core",104 "database_id": 170,105 "gcp_project": "aftership-pro",106 "env": "production",107 "product_id": 98,108 "product_name": "AfterShip Feed",109 "backend_owner": "xq.yan@aftership.com",110 "db_type": "spanner",111 "modules": ["Feed Internal", "Automizely Feed"]112 }113 ],114 "pagination": {115 "total": 1,116 "page": 1,117 "next_cursor": null,118 "limit": 20,119 "has_next_page": false120 }121 }122}123```124125Extract the `database_id` from `data.databases[0].database_id`.126127### Step 3: Execute Query Against DBP API128Once you have the token and database_id, use them to query the database through the API:129130**API Endpoint:**131```132https://api.automizely.org/dbp/v2/instances/${instance_name}/databases/${database_id}/query-result133```134135**Request Format:**136```json137{138 "db_type": "spanner",139 "gcp_project": "aftership-test", // or "aftership-pro" for production140 "query": "SELECT * FROM orders WHERE order_id='xxx' LIMIT 10;",141 "query_mode": "profile"142}143```144145### Step 4: Complete Go Script146Here's a complete Go script that handles token retrieval, database_id lookup, and database querying:147148```go149package main150151import (152 "bytes"153 "context"154 "encoding/json"155 "fmt"156 "io"157 "log"158 "net/http"159 "os"160 161 "github.com/AfterShip/go-admin-automizely-cli/client"162)163164type QueryRequest struct {165 DBType string `json:"db_type"`166 GCPProject string `json:"gcp_project"`167 Query string `json:"query"`168 QueryMode string `json:"query_mode"`169}170171type DatabaseInfo struct {172 InstanceName string `json:"instance_name"`173 DatabaseName string `json:"database_name"`174 DatabaseID int `json:"database_id"`175 GCPProject string `json:"gcp_project"`176 Env string `json:"env"`177}178179type DatabaseListResponse struct {180 Meta struct {181 Code int `json:"code"`182 Type string `json:"type"`183 Message string `json:"message"`184 } `json:"meta"`185 Data struct {186 Databases []DatabaseInfo `json:"databases"`187 } `json:"data"`188}189190func main() {191 if len(os.Args) < 5 {192 log.Fatal("Usage: go run script.go <instance_name> <database_name> <environment> <sql_query>\n" +193 " instance_name: p-connectors-usce1, aftership-pro-1, etc.\n" +194 " database_name: af-p-core, af-p-feed, etc.\n" +195 " environment: testing or production\n" +196 " sql_query: SQL query with LIMIT (max 1000)")197 }198199 instanceName := os.Args[1]200 databaseName := os.Args[2]201 environment := os.Args[3] // "testing" or "production"202 sqlQuery := os.Args[4]203204 // Get authentication token using client.GetToken205 token, err := client.GetToken(context.Background(), environment)206 if err != nil {207 log.Fatalf("Failed to get token: %v", err)208 }209210 // Determine GCP project based on environment211 gcpProject := "aftership-test"212 if environment == "production" {213 gcpProject = "aftership-pro"214 }215216 // Step 1: Get database_id by querying database list217 databaseID, err := getDatabaseID(token, instanceName, databaseName, gcpProject)218 if err != nil {219 log.Fatalf("Failed to get database ID: %v", err)220 }221222 fmt.Printf("Found database_id: %d for %s/%s\n", databaseID, instanceName, databaseName)223224 // Step 2: Execute query225 result, err := executeQuery(token, instanceName, databaseID, gcpProject, sqlQuery)226 if err != nil {227 log.Fatalf("Failed to execute query: %v", err)228 }229230 fmt.Println("\nQuery Result:")231 fmt.Println(result)232}233234func getDatabaseID(token, instanceName, databaseName, gcpProject string) (int, error) {235 url := fmt.Sprintf("https://api.automizely.org/dbp/v2/instances/%s/databases?database_name=%s&db_type=spanner&gcp_project=%s&instance=%s&limit=20&page=1",236 instanceName, databaseName, gcpProject, instanceName)237238 req, err := http.NewRequest("GET", url, nil)239 if err != nil {240 return 0, fmt.Errorf("failed to create request: %w", err)241 }242243 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))244245 client := &http.Client{}246 resp, err := client.Do(req)247 if err != nil {248 return 0, fmt.Errorf("failed to execute request: %w", err)249 }250 defer resp.Body.Close()251252 body, err := io.ReadAll(resp.Body)253 if err != nil {254 return 0, fmt.Errorf("failed to read response: %w", err)255 }256257 if resp.StatusCode != http.StatusOK {258 return 0, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))259 }260261 var dbResponse DatabaseListResponse262 if err := json.Unmarshal(body, &dbResponse); err != nil {263 return 0, fmt.Errorf("failed to unmarshal response: %w", err)264 }265266 if len(dbResponse.Data.Databases) == 0 {267 return 0, fmt.Errorf("no database found with name %s in instance %s", databaseName, instanceName)268 }269270 return dbResponse.Data.Databases[0].DatabaseID, nil271}272273func executeQuery(token, instanceName string, databaseID int, gcpProject, sqlQuery string) (string, error) {274 // Prepare request275 reqBody := QueryRequest{276 DBType: "spanner",277 GCPProject: gcpProject,278 Query: sqlQuery,279 QueryMode: "profile",280 }281282 jsonData, err := json.Marshal(reqBody)283 if err != nil {284 return "", fmt.Errorf("failed to marshal request: %w", err)285 }286287 // Make API request288 url := fmt.Sprintf("https://api.automizely.org/dbp/v2/instances/%s/databases/%d/query-result", instanceName, databaseID)289 req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))290 if err != nil {291 return "", fmt.Errorf("failed to create request: %w", err)292 }293294 req.Header.Set("Content-Type", "application/json")295 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))296297 client := &http.Client{}298 resp, err := client.Do(req)299 if err != nil {300 return "", fmt.Errorf("failed to execute request: %w", err)301 }302 defer resp.Body.Close()303304 // Read response305 body, err := io.ReadAll(resp.Body)306 if err != nil {307 return "", fmt.Errorf("failed to read response: %w", err)308 }309310 if resp.StatusCode != http.StatusOK {311 return "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))312 }313314 return string(body), nil315}316```317318## Important Constraints319320### Instance and Database Parameters321- **instance_name**: The Spanner instance name (e.g., `p-connectors-usce1`, `aftership-pro-1`)322- **database_name**: The logical database name (e.g., `af-p-core`, `af-p-feed`)323- **database_id**: Automatically retrieved from the database list API324- The script first queries the database list API to get the database_id, then uses it to execute the query325- **DDL**: When you do not know the DDL of a table, write a go script to call API `https://api.automizely.org/dbp/v2/instances/${instance_name}/databases/${database_id}/tables/${table_name}/ddl?db_type=spanner&gcp_project=aftership-pro&mode=Schemas&table_name=${table_name}`. And you can get the ddl from the response. 326response example: 327```json328{329 "meta": {330 "code": 20000,331 "type": "OK",332 "message": "The request was successfully processed by AfterShip."333 },334 "data": "CREATE TABLE job_groups (\n group_id STRING(32) NOT NULL,\n namespace STRING(256) NOT NULL,\n project STRING(256) NOT NULL,\n name STRING(256) NOT NULL,\n job_topic_name STRING(256) NOT NULL,\n notification_topic_name STRING(256),\n concurrency INT64,\n retry_config STRING(MAX) NOT NULL,\n created_at TIMESTAMP NOT NULL OPTIONS (\n allow_commit_timestamp = true\n ),\n updated_at TIMESTAMP NOT NULL OPTIONS (\n allow_commit_timestamp = true\n ),\n) PRIMARY KEY(group_id)"335}336```337338### Query Requirements339- **LIMIT is MANDATORY**: Every SQL query MUST include a LIMIT clause340- **Maximum LIMIT**: The LIMIT value cannot exceed 1000341- **Query Validation**: Always validate that the query includes LIMIT before execution342343Example valid queries:344```sql345SELECT * FROM orders WHERE order_id='xxx' LIMIT 10;346SELECT order_id, status FROM orders WHERE created_at > '2024-01-01' LIMIT 100;347SELECT COUNT(*) as count FROM orders LIMIT 1;348```349350### Environment Configuration351- **Test Environment**: Use environment `"testing"` with `gcp_project: "aftership-test"`352- **Production Environment**: Use environment `"production"` with `gcp_project: "aftership-pro"`353354### Query Modes355- **profile**: Returns query results with execution statistics and performance metrics356- Use "profile" mode by default for better debugging insights357358## Usage Examples359360### Example 1: Query Single Order from Feed Database (Production)361```bash362go run query_db.go "aftership-pro-1" "af-p-core" "production" "SELECT * FROM orders WHERE order_id='6f851c942e604330b7165aa2408047d3' LIMIT 10;"363```364365### Example 2: Query from Connectors Instance (Production)366```bash367go run query_db.go "p-connectors-usce1" "af-p-connectors" "production" "SELECT order_id, status, created_at FROM orders WHERE created_at > TIMESTAMP('2024-11-01') ORDER BY created_at DESC LIMIT 100;"368```369370### Example 3: Count Orders by Status (Test Environment)371```bash372go run query_db.go "aftership-test-1" "af-t-core" "testing" "SELECT status, COUNT(*) as count FROM orders GROUP BY status LIMIT 1000;"373```374375### Example 4: Check Inventory Records376```bash377go run query_db.go "aftership-pro-1" "af-p-core" "production" "SELECT * FROM inventory WHERE sku='ABC123' LIMIT 50;"378```379380## Error Handling381382Common errors and solutions:3833841. **Authentication Failed**: Ensure go-admin-automizely-cli is properly configured3852. **Database Not Found**: Verify the instance_name and database_name are correct3863. **Missing LIMIT**: Add LIMIT clause to your SQL query (required)3874. **LIMIT Too Large**: Reduce LIMIT to 1000 or less3885. **Invalid Instance Name**: Use correct instance names (p-connectors-usce1, aftership-pro-1, etc.)3896. **Query Timeout**: Optimize query or reduce LIMIT value3907. **Empty Database List**: Check if the database exists in the specified instance391392## Best Practices3933941. Always start with a small LIMIT (e.g., 10) for exploratory queries3952. Use specific WHERE clauses to reduce query scope3963. Use indexes when available for better performance3974. Review query execution statistics from profile mode3985. Test queries in test environment before running in production3996. Keep sensitive data secure - don't log tokens or credentials400401## Response Format402403The API returns JSON with the following structure:404- Query results as an array of records405- Execution statistics (when using profile mode)406- Column metadata407- Row count information408409## Notes410411- This tool is primarily for Spanner databases but the structure can be adapted for other database types412- Always respect data privacy and security policies when querying production data413- Use appropriate LIMIT values to avoid performance impact on production systems414415---416> Converted and distributed by [TomeVault](https://tomevault.io/claim/virgoc0der) — claim your Tome and manage your conversions.417<!-- tomevault:4.0:skill_md:2026-04-13 -->