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
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.4---5
6# Database Query Skill
7
8## Overview
9This 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.
10
11**Main Production Instances:**
12- `p-connectors-usce1` - Connectors instance
13- `aftership-pro-1` - Main AfterShip instance
14
15The script automatically retrieves the database_id by querying the database list API before executing queries.
16
17**Production Databases:**
18
19| 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` |
27
28
29
30## When to Use This Skill
31Use this skill when Billy needs to:
32- Query Spanner database records
33- Investigate data issues or verify data states
34- Fetch specific records for debugging or analysis
35- Profile query performance
36
37## Prerequisites
38- Go environment set up
39- Access to https://github.com/AfterShip/go-admin-automizely-cli library
40- Appropriate permissions to access the databases
41
42## Implementation Steps
43
44### Step 1: Install and Setup
45First, install the go-admin-automizely-cli library:
46
47```bash
48go get -u github.com/AfterShip/go-admin-automizely-cli
49```
50
51### Step 2: Get Token
52Create a Go script that uses the client.GetToken method to obtain an authentication token:
53
54```go
55package main
56
57import (
58 "context"
59 "fmt"
60 "log"
61
62 "github.com/AfterShip/go-admin-automizely-cli/client"
63)
64
65func main() {
66 // Use "testing" for test environment or "production" for production
67 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```
74
75**Environment Options:**
76- `"testing"` - For test environment (aftership-test)
77- `"production"` - For production environment (aftership-pro)
78
79### Step 2: Get Database ID (if needed)
80If you don't know the database_id, first query the database list API to get it:
81
82**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=1
85```
86
87**Common Production Instances:**
88- `p-connectors-usce1` - Connectors instance
89- `aftership-pro-1` - Main AfterShip instance
90
91**Example Response:**
92```json
93{
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": false
120 }
121 }
122}
123```
124
125Extract the `database_id` from `data.databases[0].database_id`.
126
127### Step 3: Execute Query Against DBP API
128Once you have the token and database_id, use them to query the database through the API:
129
130**API Endpoint:**
131```
132https://api.automizely.org/dbp/v2/instances/${instance_name}/databases/${database_id}/query-result
133```
134
135**Request Format:**
136```json
137{
138 "db_type": "spanner",
139 "gcp_project": "aftership-test", // or "aftership-pro" for production
140 "query": "SELECT * FROM orders WHERE order_id='xxx' LIMIT 10;",
141 "query_mode": "profile"
142}
143```
144
145### Step 4: Complete Go Script
146Here's a complete Go script that handles token retrieval, database_id lookup, and database querying:
147
148```go
149package main
150
151import (
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)
163
164type 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}
170
171type 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}
178
179type 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}
189
190func 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 }
198
199 instanceName := os.Args[1]
200 databaseName := os.Args[2]
201 environment := os.Args[3] // "testing" or "production"
202 sqlQuery := os.Args[4]
203
204 // Get authentication token using client.GetToken
205 token, err := client.GetToken(context.Background(), environment)
206 if err != nil {
207 log.Fatalf("Failed to get token: %v", err)
208 }
209
210 // Determine GCP project based on environment
211 gcpProject := "aftership-test"
212 if environment == "production" {
213 gcpProject = "aftership-pro"
214 }
215
216 // Step 1: Get database_id by querying database list
217 databaseID, err := getDatabaseID(token, instanceName, databaseName, gcpProject)
218 if err != nil {
219 log.Fatalf("Failed to get database ID: %v", err)
220 }
221
222 fmt.Printf("Found database_id: %d for %s/%s\n", databaseID, instanceName, databaseName)
223
224 // Step 2: Execute query
225 result, err := executeQuery(token, instanceName, databaseID, gcpProject, sqlQuery)
226 if err != nil {
227 log.Fatalf("Failed to execute query: %v", err)
228 }
229
230 fmt.Println("\nQuery Result:")
231 fmt.Println(result)
232}
233
234func 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)
237
238 req, err := http.NewRequest("GET", url, nil)
239 if err != nil {
240 return 0, fmt.Errorf("failed to create request: %w", err)
241 }
242
243 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
244
245 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()
251
252 body, err := io.ReadAll(resp.Body)
253 if err != nil {
254 return 0, fmt.Errorf("failed to read response: %w", err)
255 }
256
257 if resp.StatusCode != http.StatusOK {
258 return 0, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
259 }
260
261 var dbResponse DatabaseListResponse
262 if err := json.Unmarshal(body, &dbResponse); err != nil {
263 return 0, fmt.Errorf("failed to unmarshal response: %w", err)
264 }
265
266 if len(dbResponse.Data.Databases) == 0 {
267 return 0, fmt.Errorf("no database found with name %s in instance %s", databaseName, instanceName)
268 }
269
270 return dbResponse.Data.Databases[0].DatabaseID, nil
271}
272
273func executeQuery(token, instanceName string, databaseID int, gcpProject, sqlQuery string) (string, error) {
274 // Prepare request
275 reqBody := QueryRequest{
276 DBType: "spanner",
277 GCPProject: gcpProject,
278 Query: sqlQuery,
279 QueryMode: "profile",
280 }
281
282 jsonData, err := json.Marshal(reqBody)
283 if err != nil {
284 return "", fmt.Errorf("failed to marshal request: %w", err)
285 }
286
287 // Make API request
288 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 }
293
294 req.Header.Set("Content-Type", "application/json")
295 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
296
297 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()
303
304 // Read response
305 body, err := io.ReadAll(resp.Body)
306 if err != nil {
307 return "", fmt.Errorf("failed to read response: %w", err)
308 }
309
310 if resp.StatusCode != http.StatusOK {
311 return "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
312 }
313
314 return string(body), nil
315}
316```
317
318## Important Constraints
319
320### Instance and Database Parameters
321- **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 API
324- The script first queries the database list API to get the database_id, then uses it to execute the query
325- **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```json
328{
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```
337
338### Query Requirements
339- **LIMIT is MANDATORY**: Every SQL query MUST include a LIMIT clause
340- **Maximum LIMIT**: The LIMIT value cannot exceed 1000
341- **Query Validation**: Always validate that the query includes LIMIT before execution
342
343Example valid queries:
344```sql
345SELECT * 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```
349
350### Environment Configuration
351- **Test Environment**: Use environment `"testing"` with `gcp_project: "aftership-test"`
352- **Production Environment**: Use environment `"production"` with `gcp_project: "aftership-pro"`
353
354### Query Modes
355- **profile**: Returns query results with execution statistics and performance metrics
356- Use "profile" mode by default for better debugging insights
357
358## Usage Examples
359
360### Example 1: Query Single Order from Feed Database (Production)
361```bash
362go run query_db.go "aftership-pro-1" "af-p-core" "production" "SELECT * FROM orders WHERE order_id='6f851c942e604330b7165aa2408047d3' LIMIT 10;"
363```
364
365### Example 2: Query from Connectors Instance (Production)
366```bash
367go 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```
369
370### Example 3: Count Orders by Status (Test Environment)
371```bash
372go run query_db.go "aftership-test-1" "af-t-core" "testing" "SELECT status, COUNT(*) as count FROM orders GROUP BY status LIMIT 1000;"
373```
374
375### Example 4: Check Inventory Records
376```bash
377go run query_db.go "aftership-pro-1" "af-p-core" "production" "SELECT * FROM inventory WHERE sku='ABC123' LIMIT 50;"
378```
379
380## Error Handling
381
382Common errors and solutions:
383
3841. **Authentication Failed**: Ensure go-admin-automizely-cli is properly configured
3852. **Database Not Found**: Verify the instance_name and database_name are correct
3863. **Missing LIMIT**: Add LIMIT clause to your SQL query (required)
3874. **LIMIT Too Large**: Reduce LIMIT to 1000 or less
3885. **Invalid Instance Name**: Use correct instance names (p-connectors-usce1, aftership-pro-1, etc.)
3896. **Query Timeout**: Optimize query or reduce LIMIT value
3907. **Empty Database List**: Check if the database exists in the specified instance
391
392## Best Practices
393
3941. Always start with a small LIMIT (e.g., 10) for exploratory queries
3952. Use specific WHERE clauses to reduce query scope
3963. Use indexes when available for better performance
3974. Review query execution statistics from profile mode
3985. Test queries in test environment before running in production
3996. Keep sensitive data secure - don't log tokens or credentials
400
401## Response Format
402
403The API returns JSON with the following structure:
404- Query results as an array of records
405- Execution statistics (when using profile mode)
406- Column metadata
407- Row count information
408
409## Notes
410
411- This tool is primarily for Spanner databases but the structure can be adapted for other database types
412- Always respect data privacy and security policies when querying production data
413- Use appropriate LIMIT values to avoid performance impact on production systems