Skip to content

Commit de02905

Browse files
committed
feat(sql): add model-based CRUD support with Active Record-style API
Introduce a new model-based CRUD mode for the Sql executor allowing declarative table schema definitions and Active Record-style operations (insert, select, update, delete, upsert). This includes: - Model schema definitions with columns, types, indexes, and constraints - SQL DDL generation for creating tables and indexes - Query builder generating parameterized SQL for different database engines - Input validation enforcing mutually exclusive raw SQL vs model mode - Updated Sql executor and input/output schemas to support dual modes - Documentation and examples for using model mode - Migration of existing workflows and capabilities to use model mode for state management Also remove legacy state-management agent workflows and replace with new model-driven implementations.
1 parent f314519 commit de02905

57 files changed

Lines changed: 4795 additions & 6806 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/llm/block-reference.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Field names are **exact** - use them precisely in your workflows.
2222
| ReadJSONState | path |
2323
| WriteJSONState | path, data |
2424
| MergeJSONState | path, updates |
25+
| Sql | engine |
2526

2627
### Common Field Name Mistakes
2728

@@ -484,6 +485,145 @@ Without this, execution fails with configuration error.
484485

485486
---
486487

488+
## Sql
489+
490+
**Description**: SQL executor for database operations.
491+
492+
### Required Inputs
493+
494+
- **`engine`** (string): Database engine. Required.
495+
496+
### Optional Inputs
497+
498+
- **`path`** (any): SQLite: Database file path. Use ':memory:' for in-memory DB.
499+
- **`host`** (any): Database host
500+
- **`port`** (any): Database port (default: 5432 for PostgreSQL, 3306 for MariaDB)
501+
- **`database`** (any): Database name
502+
- **`username`** (any): Database username
503+
- **`password`** (any): Database password. Use {{secrets.DB_PASSWORD}} for security.
504+
- **`sql`** (any):
505+
SQL statement(s) to execute (Raw SQL mode).
506+
- Use ? for positional params (SQLite) or $1, $2 for PostgreSQL
507+
- MariaDB uses %s for positional params
508+
- Multi-statement scripts: separate with semicolons
509+
Mutually exclusive with 'model' field.
510+
511+
- **`params`** (any):
512+
Query parameters for raw SQL (prevents SQL injection).
513+
- List for positional: [value1, value2]
514+
- Dict for named: {"name": value} (PostgreSQL/MariaDB)
515+
516+
- **`model`** (any):
517+
Model schema for CRUD operations (Model mode).
518+
Defines table structure with columns, types, indexes.
519+
Mutually exclusive with 'sql' field.
520+
Example:
521+
model:
522+
table: tasks
523+
columns:
524+
id: {type: text, primary: true, auto: uuid}
525+
name: {type: text, required: true}
526+
indexes:
527+
- columns: [name]
528+
529+
- **`op`** (any):
530+
CRUD operation (required when using model mode).
531+
- schema: Create table + indexes
532+
- insert: Insert row (requires data)
533+
- select: Query rows (optional where, order, limit, offset)
534+
- update: Update rows (requires where and data)
535+
- delete: Delete rows (requires where)
536+
- upsert: Insert or update on conflict (requires data and conflict)
537+
538+
- **`data`** (any): Row data for insert/update/upsert operations.
539+
- **`where`** (any):
540+
Filter conditions for select/update/delete.
541+
- Simple equality: {status: running}
542+
- Operators: {priority: {">": 5}}
543+
- IN: {type: {in: [a, b, c]}}
544+
- IS NULL: {deleted_at: {is: null}}
545+
546+
- **`order`** (any): Sort order for select. Format: ["column:asc", "column:desc"]
547+
- **`limit`** (any): Maximum rows to return (select).
548+
- **`offset`** (any): Rows to skip (select).
549+
- **`conflict`** (any): Conflict columns for upsert (usually primary key).
550+
- **`init_sql`** (any):
551+
DDL to execute before the main operation (idempotent).
552+
Use CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, etc.
553+
554+
- **`isolation_level`** (any):
555+
Transaction isolation level.
556+
- PostgreSQL/MariaDB: read_uncommitted, read_committed, repeatable_read, serializable
557+
- SQLite: immediate (recommended for writes), exclusive, or default (deferred)
558+
559+
- **`ssl`** (any) *(default: `False`)*: Enable SSL/TLS. Boolean or sslmode string (require, verify-ca, verify-full)
560+
- **`timeout`** (any) *(default: `30`)*: Query execution timeout in seconds
561+
- **`connect_timeout`** (any) *(default: `10`)*: Connection establishment timeout in seconds
562+
- **`pool_size`** (any) *(default: `5`)*: Connection pool size (PostgreSQL/MariaDB only)
563+
- **`sqlite_pragmas`** (any):
564+
SQLite PRAGMA settings applied on connection.
565+
Defaults: journal_mode=WAL, busy_timeout=30000, synchronous=NORMAL, foreign_keys=ON
566+
567+
568+
### Outputs
569+
570+
- **`meta`** (object): Executor-specific metadata fields (exit_code, tokens_used, etc.)
571+
- **`rows`** (array): Result rows as list of dicts
572+
- **`columns`** (array): Column names from result set
573+
- **`row_count`** (integer): Number of rows returned (select) or affected (insert/update/delete)
574+
- **`affected_rows`** (integer): Rows affected by INSERT/UPDATE/DELETE
575+
- **`last_insert_id`** (any): Last inserted row ID (auto-increment)
576+
- **`success`** (boolean): Operation completed successfully
577+
- **`engine`** (string): Database engine used
578+
- **`execution_time_ms`** (number): Query execution time in milliseconds
579+
580+
### Example
581+
582+
```yaml
583+
# Raw SQL mode - SQLite query
584+
- id: get_users
585+
type: Sql
586+
inputs:
587+
engine: sqlite
588+
path: "/data/app.db"
589+
sql: "SELECT * FROM users WHERE status = ?"
590+
params: ["active"]
591+
592+
# Model mode - Create table and insert
593+
- id: create_task
594+
type: Sql
595+
inputs:
596+
engine: sqlite
597+
path: "{{state.db_path}}"
598+
model:
599+
table: tasks
600+
columns:
601+
task_id: {type: text, primary: true, auto: uuid}
602+
name: {type: text, required: true}
603+
status: {type: text, default: pending}
604+
created_at: {type: timestamp, auto: created}
605+
indexes:
606+
- columns: [status]
607+
op: insert
608+
data:
609+
name: "My Task"
610+
611+
# Model mode - Select with filters
612+
- id: find_tasks
613+
type: Sql
614+
inputs:
615+
engine: sqlite
616+
path: "{{state.db_path}}"
617+
model: "{{inputs.models.task}}"
618+
op: select
619+
where:
620+
status: running
621+
order: [created_at:desc]
622+
limit: 10
623+
```
624+
625+
---
626+
487627
## Variable Access Patterns
488628

489629
Use double braces `{{ }}` for variable interpolation in YAML:

0 commit comments

Comments
 (0)