| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- package pizzasql
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strings"
- )
- // Client represents a connection to a PizzaSQL database
- type Client struct {
- baseURL string
- dbName string
- apiKey string
- client *http.Client
- }
- // Row represents a single row in the result set
- type Row map[string]interface{}
- // QueryResult represents the result of a SQL query
- type QueryResult struct {
- Rows []Row `json:"rows"`
- }
- // Connect creates a new PizzaSQL client connection
- // URI format: http://host:port/dbname or https://pizzabase.cloud/my_org/sql/my_db:32131
- func Connect(uri string, apiKey string) (*Client, error) {
- parsedURL, err := url.Parse(uri)
- if err != nil {
- return nil, fmt.Errorf("invalid URI: %w", err)
- }
- // Extract database name from path
- path := strings.Trim(parsedURL.Path, "/")
- if path == "" {
- return nil, fmt.Errorf("database name not found in URI path")
- }
- // Split path to get database name (last segment)
- pathParts := strings.Split(path, "/")
- dbName := pathParts[len(pathParts)-1]
- // Reconstruct base URL without the database path
- baseURL := fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host)
- return &Client{
- baseURL: baseURL,
- dbName: dbName,
- apiKey: apiKey,
- client: &http.Client{},
- }, nil
- }
- // SQL executes a SQL query and returns the results as a slice of rows
- func (c *Client) SQL(query string) ([]Row, error) {
- // Prepare request body
- body := map[string]string{"query": query}
- jsonBody, err := json.Marshal(body)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal request: %w", err)
- }
- // Create request
- url := fmt.Sprintf("%s/%s/query", c.baseURL, c.dbName)
- req, err := http.NewRequest("POST", url, bytes.NewReader(jsonBody))
- if err != nil {
- return nil, fmt.Errorf("failed to create request: %w", err)
- }
- // Set headers
- req.Header.Set("Content-Type", "application/json")
- if c.apiKey != "" {
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
- }
- // Execute request
- resp, err := c.client.Do(req)
- if err != nil {
- return nil, fmt.Errorf("request failed: %w", err)
- }
- defer resp.Body.Close()
- // Read response body
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, fmt.Errorf("failed to read response: %w", err)
- }
- // Check status code
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(respBody))
- }
- // Parse response
- var result QueryResult
- if err := json.Unmarshal(respBody, &result); err != nil {
- return nil, fmt.Errorf("failed to parse response: %w", err)
- }
- return result.Rows, nil
- }
- // Export exports a database or table to SQL or CSV format
- func (c *Client) Export(table string, format string) ([]byte, error) {
- params := url.Values{}
- if table != "" {
- params.Set("table", table)
- }
- if format != "" {
- params.Set("format", format)
- }
- url := fmt.Sprintf("%s/%s/export?%s", c.baseURL, c.dbName, params.Encode())
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return nil, fmt.Errorf("failed to create request: %w", err)
- }
- if c.apiKey != "" {
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
- }
- resp, err := c.client.Do(req)
- if err != nil {
- return nil, fmt.Errorf("request failed: %w", err)
- }
- defer resp.Body.Close()
- data, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, fmt.Errorf("failed to read response: %w", err)
- }
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("export failed with status %d: %s", resp.StatusCode, string(data))
- }
- return data, nil
- }
- // Import imports data from SQL or CSV format
- func (c *Client) Import(data []byte, format string, createTable bool) error {
- params := url.Values{}
- if format != "" {
- params.Set("format", format)
- }
- if createTable {
- params.Set("create_table", "true")
- }
- url := fmt.Sprintf("%s/%s/import?%s", c.baseURL, c.dbName, params.Encode())
- req, err := http.NewRequest("POST", url, bytes.NewReader(data))
- if err != nil {
- return fmt.Errorf("failed to create request: %w", err)
- }
- req.Header.Set("Content-Type", "application/octet-stream")
- if c.apiKey != "" {
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
- }
- resp, err := c.client.Do(req)
- if err != nil {
- return fmt.Errorf("request failed: %w", err)
- }
- defer resp.Body.Close()
- respBody, 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("import failed with status %d: %s", resp.StatusCode, string(respBody))
- }
- return nil
- }
|