// Auto-generated from src/ai-service/skills/system/superblocks-api/references/sql-databases.md
// Do not edit directly - edit the .md file instead
export const content = `# SQL Database Integrations

Reference documentation for working with SQL databases in Superblocks APIs.

## Supported Databases

- PostgreSQL, MySQL, MariaDB, MSSQL
- Snowflake, Redshift, BigQuery, Athena
- Databricks, SingleStore, Lakebase
- CockroachDB, YugabyteDB, OracleDB

## SQL Block Classes

\`\`\`typescript
// Available SQL integrations
import {
  PostgreSQL,
  MySQL,
  MariaDB,
  MSSQL,
  Snowflake,
  Redshift,
  BigQuery,
  Athena,
  Databricks,
  SingleStore,
  OracleDB,
} from "@superblocksteam/library";
\`\`\`

## Basic Usage

\`\`\`typescript
new PostgreSQL("get_users", "postgres-integration-id", {
  statement: "SELECT * FROM users WHERE active = true LIMIT 100",
});
\`\`\`

## Parameterized Queries

### SQL Security Priority

**ALWAYS prefer parameterized queries over string interpolation for user input values.**

Use \`parameters: "[var1, var2]"\` with the database-specific SQL placeholders:

| Database                                    | Placeholder Syntax     | Example                                     |
| ------------------------------------------- | ---------------------- | ------------------------------------------- |
| PostgreSQL, Redshift, CockroachDB           | \`$1\`, \`$2\`, \`$3\`       | \`WHERE id = $1 AND status = $2\`             |
| MySQL, MariaDB, Snowflake, BigQuery, Athena | \`?\`, \`?\`, \`?\`          | \`WHERE id = ? AND status = ?\`               |
| MSSQL                                       | \`@PARAM_1\`, \`@PARAM_2\` | \`WHERE id = @PARAM_1 AND status = @PARAM_2\` |
| Databricks                                  | \`:PARAM_1\`, \`:PARAM_2\` | \`WHERE id = :PARAM_1 AND status = :PARAM_2\` |
| OracleDB                                    | \`:1\`, \`:2\`, \`:3\`       | \`WHERE id = :1 AND status = :2\`             |

Only use binding functions for truly dynamic SQL elements like table/column names.

### Parameters Syntax vs Statement Syntax

**Important:** \`parameters\` is a JavaScript expression string that gets evaluated directly. Do NOT use \`\${}\` interpolation or binding functions.

\`\`\`typescript
// ❌ WRONG - Don't use \${} interpolation in parameters
parameters: "[\${build_query.output.userId}, \${searchTerm}]";

// ❌ WRONG - Don't use binding functions for parameters
parameters: ({ build_query }) => JSON.stringify(build_query.output.params);

// ✅ CORRECT - Write expressions directly (they get evaluated as JS)
parameters: "[build_query.output.userId, searchTerm, limit]";

// ✅ CORRECT - Reference block output array directly
parameters: "build_query.output.params";
\`\`\`

This is different from \`statement\`, where binding functions with \`\${}\` ARE used to interpolate values into SQL.

### String Interpolation (Alternative)

For simpler cases, string interpolation also works:

\`\`\`typescript
// ✅ CORRECT - String interpolation for WHERE clauses
new PostgreSQL("search", "postgres-id", {
  statement: ({ searchTerm, limit }: { searchTerm: string; limit: number }) =>
    \`SELECT * FROM users WHERE name ILIKE '%\${searchTerm}%' LIMIT \${limit}\`,
});

// ✅ CORRECT - For INSERT statements
new PostgreSQL("insert_user", "postgres-id", {
  statement: ({ name, email }: { name: string; email: string }) =>
    \`INSERT INTO users (name, email) VALUES ('\${name}', '\${email}') RETURNING *\`,
});
\`\`\`

### Dynamic SQL with Optional Filters

Use the \`parameters\` array with the "OR NULL" pattern for optional filters:

- \`WHERE (column = $1 OR $1::type IS NULL)\` makes the condition a no-op when parameter is null
- Cast parameters to their type when checking for NULL (PostgreSQL can't infer types from NULL values)
- Always pass all parameters, using null for unused filters
- NEVER interpolate user values directly into SQL strings

## Best Practices

### 1. ONE Query Per Block Rule

Each SQL block (PostgreSQL, Snowflake, MySQL, MicrosoftSql, Databricks) can execute **ONLY ONE SQL query**.

❌ **WRONG - Multiple queries in one block:**

\`\`\`sql
UPDATE users SET status = 'active';
DELETE FROM logs WHERE created < '2023-01-01';
INSERT INTO audit VALUES ('done');
\`\`\`

✅ **CORRECT - One query per block:**

\`\`\`typescript
(new PostgreSQL("update_status", "postgres-id", {
  statement: "UPDATE users SET status = 'active'",
}),
  new PostgreSQL("clean_logs", "postgres-id", {
    statement: "DELETE FROM logs WHERE created < '2023-01-01'",
  }));
\`\`\`

### 2. Use Meaningful Block Names

\`\`\`typescript
// ✅ GOOD
new PostgreSQL("fetch_active_users", "pg-id", { ... })
new PostgreSQL("insert_order_item", "pg-id", { ... })

// ❌ BAD
new PostgreSQL("query1", "pg-id", { ... })
new PostgreSQL("sql", "pg-id", { ... })
\`\`\`

### 3. Always Specify Columns

\`\`\`typescript
// ✅ PREFERRED - Explicit columns
SELECT id, name, email, created_at FROM users

// ⚠️ AVOID - Select all
SELECT * FROM users
\`\`\`

### 4. Always Add Defensive Row Limits

Always include a row limit clause to prevent runaway queries. Use 100 as the default unless user specifies otherwise.

Different SQL dialects have different syntax:

- **MySQL/PostgreSQL/SQLite**: \`LIMIT N\`
- **SQL Server**: \`SELECT TOP N\`
- **ANSI SQL (SQL Server, Oracle, DB2)**: \`OFFSET 0 ROWS FETCH NEXT N ROWS ONLY\`

\`\`\`sql
-- ✅ PostgreSQL/MySQL
SELECT * FROM orders ORDER BY created_at DESC LIMIT 100;

-- ✅ SQL Server
SELECT TOP 100 * FROM orders ORDER BY created_at DESC;

-- ✅ ANSI SQL (SQL Server with ORDER BY)
SELECT * FROM orders ORDER BY created_at DESC OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;

-- ❌ Avoid unlimited queries
SELECT * FROM orders ORDER BY created_at DESC;  -- Can timeout or crash
\`\`\`

### 5. Sort, Don't Filter by Date (Default)

Do NOT add automatic date filters (e.g., "last 90 days") unless the user explicitly requests them. This is a common cause of unexpected empty SQL results.

\`\`\`sql
-- ✅ Default approach
SELECT * FROM orders ORDER BY created_at DESC LIMIT 100;

-- ❌ Avoid automatic filtering
SELECT * FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '90 days';  -- Can return empty results
\`\`\`

Only add date WHERE clauses when users explicitly ask for time-based filtering.

### 6. Handle NULL Values

\`\`\`typescript
// ✅ Use COALESCE for default values
SELECT COALESCE(description, 'No description') as description FROM products

// ✅ Use IS NULL / IS NOT NULL
SELECT * FROM users WHERE deleted_at IS NULL
\`\`\`

## Database-Specific Notes

### PostgreSQL

\`\`\`typescript
// Array operations
new PostgreSQL("array_query", "pg-id", {
  statement: ({ tags }: { tags: string[] }) =>
    \`SELECT * FROM posts WHERE tags && ARRAY[\${tags.map((t) => \`'\${t}'\`).join(",")}]\`,
});

// JSON operations
new PostgreSQL("json_query", "pg-id", {
  statement: "SELECT data->>'name' as name FROM json_data",
});
\`\`\`

- Use schema-qualified names: \`schema.table\` or \`"schema"."table"\`
- Case sensitivity: Unquoted identifiers are lowercase
- Use \`::type\` for type casting

#### Custom Type Casts (Enums, Composite Types)

**When casting parameters, always use the actual column type — not \`text\` or other generic types.**

PostgreSQL columns can use custom types such as enums (\`billing_plan_enum\`, \`order_status_enum\`) or composite types. When \`grepMetadata\` reports a column type that is not a built-in type, you **must** use that exact type in SQL casts. Casting to \`text\` or \`text[]\` will fail with an \`operator does not exist\` error.

**Rule:** Before writing any \`::type\` cast, check the actual column type via \`grepMetadata\`. If the column type is a custom type (e.g., \`billing_plan_enum\`), cast to that type, not \`text\`.

\`\`\`sql
-- grepMetadata shows: billing.plan has type "billing_plan_enum"

-- ✅ CORRECT - Cast to the actual column type
WHERE bf.plan = ANY($3::billing_plan_enum[])

-- ❌ WRONG - Casting to text[] for an enum column
WHERE bf.plan = ANY($3::text[])
-- ERROR: operator does not exist: billing_plan_enum = text
\`\`\`

This applies to all custom types, not just enums:

\`\`\`sql
-- Column type is "order_status_enum"
WHERE o.status = ANY($1::order_status_enum[])  -- ✅
WHERE o.status = ANY($1::text[])               -- ❌

-- Column type is "priority_level" (custom enum)
WHERE t.priority = $2::priority_level           -- ✅
WHERE t.priority = $2::text                     -- ❌
\`\`\`

**Why this matters:** PostgreSQL does not implicitly cast between custom types and \`text\`. Even though enum values look like strings and \`SELECT DISTINCT\` returns them as plain strings, the column's actual type must be used in parameter casts. The \`grepMetadata\` tool returns the real type name — always use it.

### Snowflake

\`\`\`typescript
// Use QUALIFY for window function filtering
new Snowflake("ranked_query", "snowflake-id", {
  statement: \`
    SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) as rank
    FROM products
    QUALIFY rank <= 3
  \`,
});
\`\`\`

**Snowflake Two-Part Naming:** Snowflake uses \`schema.table\`

When you see Snowflake metadata where tables have a \`schema\` property, **you MUST qualify table names**:

\`\`\`sql
-- If table has schema property "MASTERDATA"
SELECT * FROM MASTERDATA.PRODUCTLINE  -- ✅ Fully qualified
SELECT * FROM PRODUCTLINE             -- ❌ Will fail if no default schema
\`\`\`

**Rule**: If a table object has a non-empty \`schema\` property, use \`{schema}.{name}\` format in your SQL queries.

### Databricks

**Databricks Three-Part Naming:** Databricks uses \`catalog.schema.table\`

When you see Databricks metadata like:

- \`uber.default.orders\` - Use the FULL path: \`uber.default.orders\`
- \`production.analytics.users\` - Use: \`production.analytics.users\`

\`\`\`sql
-- Metadata shows: uber.default.orders
SELECT * FROM uber.default.orders     -- ✅ Full path
SELECT * FROM uber.orders            -- ❌ Missing schema part
SELECT * FROM orders                 -- ❌ Missing catalog and schema
\`\`\`

**Note:** The word "default" in Databricks paths is NOT optional - it's the actual schema name. Always include all three parts exactly as shown in the metadata.

### BigQuery

\`\`\`typescript
// Use backticks for table names, ? placeholders for parameters
new BigQuery("get_events", "bq-id", {
  sqlBody:
    "SELECT * FROM \\\`project.dataset.events\\\` WHERE status = ? LIMIT 100",
  parameters: "[statusFilter]",
});

// Without parameters
new BigQuery("get_all", "bq-id", {
  sqlBody: "SELECT * FROM \\\`project.dataset.table\\\` LIMIT 100",
});
\`\`\`

## Common Patterns

### Pagination

\`\`\`typescript
new PostgreSQL("paginated_fetch", "pg-id", {
  statement: ({ page, pageSize }: { page: number; pageSize: number }) =>
    \`SELECT * FROM orders ORDER BY created_at DESC LIMIT \${pageSize} OFFSET \${(page - 1) * pageSize}\`,
});
\`\`\`

### Search with Multiple Conditions

\`\`\`typescript
new PostgreSQL("advanced_search", "pg-id", {
  statement: ({
    name,
    status,
    minAmount,
  }: {
    name?: string;
    status?: string;
    minAmount?: number;
  }) => {
    const conditions = ["1=1"];
    if (name) conditions.push(\`name ILIKE '%\${name}%'\`);
    if (status) conditions.push(\`status = '\${status}'\`);
    if (minAmount !== undefined) conditions.push(\`amount >= \${minAmount}\`);
    return \`SELECT * FROM orders WHERE \${conditions.join(" AND ")} LIMIT 100\`;
  },
});
\`\`\`

### Batch Insert

\`\`\`typescript
new PostgreSQL("batch_insert", "pg-id", {
  statement: ({ items }: { items: Array<{ name: string; value: number }> }) => {
    const values = items
      .map((item) => \`('\${item.name}', \${item.value})\`)
      .join(", ");
    return \`INSERT INTO items (name, value) VALUES \${values} RETURNING *\`;
  },
});
\`\`\`

### Upsert (Insert or Update)

\`\`\`typescript
// PostgreSQL
new PostgreSQL("upsert_user", "pg-id", {
  statement: ({
    id,
    name,
    email,
  }: {
    id: string;
    name: string;
    email: string;
  }) =>
    \`INSERT INTO users (id, name, email) 
     VALUES ('\${id}', '\${name}', '\${email}')
     ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email
     RETURNING *\`,
});
\`\`\`

## Error Handling

SQL errors are returned in the block's output. Common errors:

- Syntax errors in SQL
- Table/column doesn't exist
- Constraint violations
- Connection timeouts

For critical operations, wrap in TryCatch:

\`\`\`typescript
new TryCatch("safe_delete", {
  try: [
    new PostgreSQL("delete_record", "pg-id", {
      statement: ({ id }: { id: string }) =>
        \`DELETE FROM sensitive_data WHERE id = '\${id}'\`,
    }),
  ],
  catch: [
    new JavaScript("log_error", {
      fn: ({ deleteError }) => ({
        success: false,
        error: deleteError.value.message,
      }),
    }),
  ],
  variables: { error: "deleteError" },
});
\`\`\`
`;
//# sourceMappingURL=sql-databases.generated.js.map