Skip to main content

Database Operations

C-slop treats databases as first-class citizens with the @ symbol providing direct table access.

Configuration

Configure your database connection at the start of your app:

// PostgreSQL
@:postgres(env(DB_URL))

// MySQL
@:mysql(env(DB_URL))

// SQLite
@:sqlite("./db.sqlite")

// MongoDB
@:mongo(env(MONGO_URL))

Basic Operations

Select All

@users

Returns all rows from the users table.

Select by ID

@users[123]

Get a single record by primary key.

Select Specific Fields 🚧 WIP

@users[name, email]

Only return the name and email columns.

Combine ID and Fields 🚧 WIP

@users[123][name, email]

Get specific fields for a single record.

Filtering 🚧 WIP

Use the ? operator to filter results:

Simple Filter

@users?{active:true}

Note: Only simple equality filters work currently.

Multiple Conditions (AND) 🚧 WIP

@users?{active:true, role:"admin"}

Comparison Operators 🚧 WIP

// Greater than
@users?{age>18}

// Less than
@products?{price<100}

// Greater than or equal
@orders?{total>=50}

// Not equal
@users?{status!="banned"}

String Matching 🚧 WIP

// Contains (LIKE %query%)
@users?{name~"john"}

// Starts with
@users?{email*"admin@"}

// Ends with
@users?{email$"@company.com"}

OR Conditions 🚧 WIP

@users?{role:"admin" || role:"moderator"}

IN Operator 🚧 WIP

@users?{id:[1,2,3,4,5]}

Null Checks 🚧 WIP

// IS NULL
@users?{deletedAt:null}

// IS NOT NULL
@users?{deletedAt!null}

Sorting 🚧 WIP

// Ascending (default)
@users*name

// Descending
@users*-createdAt

// Multiple sorts
@users*-createdAt*name

Pagination 🚧 WIP

Use :limit:offset syntax:

// First 10 records
@users:10

// Skip 20, take 10
@users:10:20

// With filter and sort
@users?{active:true}*-createdAt:20:0

Insert

Use the ! operator to insert data:

@users!{
name: "John Doe",
email: "john@example.com",
createdAt: now
}

Returns the inserted record with generated ID.

Bulk Insert 🚧 WIP

@users![
{name:"Alice", email:"alice@x.com"},
{name:"Bob", email:"bob@x.com"},
{name:"Charlie", email:"charlie@x.com"}
]

Update

Combine ID selection with ! to update:

@users[123]!{
name: "Jane Doe",
updatedAt: now
}

Conditional Update 🚧 WIP

@users?{email:"old@x.com"}!{email:"new@x.com"}

Updates all matching records.

Delete

Use !- to delete:

// Delete by ID
@users[123]!-

// Delete with filter
@users?{inactive:true, createdAt<now-31536000}!-

Joins 🚧 WIP

C-slop automatically joins tables using foreign key relationships:

// Get users with their posts
@users.posts

// Get posts with author info
@posts.users

// Get specific user's posts
@users[123].posts

// Get post with author
@posts[456].users

Multiple Joins 🚧 WIP

// Posts with author and comments
@posts.users.comments

Select Fields Across Joins 🚧 WIP

@posts.users[
posts.title,
posts.body,
users.name,
users.email
]

Aggregations 🚧 WIP

Count 🚧 WIP

@users.count
@users?{active:true}.count

Sum 🚧 WIP

@orders.sum(total)
@orders?{status:"paid"}.sum(total)

Average 🚧 WIP

@products.avg(price)

Min/Max 🚧 WIP

@products.min(price)
@products.max(price)

Group By 🚧 WIP

@orders.group(userId).sum(total)

Transactions 🚧 WIP

Wrap operations in a transaction:

@tx({
// Deduct from sender
@accounts[fromId]!{balance: _.balance - amount}

// Add to recipient
@accounts[toId]!{balance: _.balance + amount}

// Create transfer record
@transfers!{fromId, toId, amount, ts:now}
})

If any operation fails, all changes are rolled back.

Raw SQL 🚧 WIP

For complex queries not covered by the DSL:

@raw("
SELECT u.name, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id
HAVING post_count > 10
")

Parameterized Queries 🚧 WIP

@raw("SELECT * FROM users WHERE email = ?", [$.query.email])

Schema Definition 🚧 WIP

Optional schema hints for type safety and migrations:

@users: {
id: int.pk.auto // Primary key, auto-increment
name: str.required // Required string
email: str.unique // Unique constraint
age: int // Optional integer
bio: text // Long text
active: bool.default(true) // Boolean with default
role: enum("user", "admin") // Enum type
balance: decimal(10,2) // Decimal with precision
avatar: str.nullable // Explicitly nullable
createdAt: time.default(now) // Timestamp
updatedAt: time.onUpdate(now)// Auto-update timestamp
}

@posts: {
id: int.pk.auto
userId: int.fk(@users) // Foreign key to users table
title: str
body: text
published: bool.default(false)
createdAt: time.default(now)
}

@comments: {
id: int.pk.auto
postId: int.fk(@posts)
userId: int.fk(@users)
body: text
createdAt: time.default(now)
}

Indexes 🚧 WIP

Define indexes for performance:

@users.index(email)
@posts.index(userId, createdAt)
@posts.index.unique(slug)
@products.index.fulltext(name, description)

Migrations 🚧 WIP

Generate migrations from schema changes:

cslop migrate:make "add_users_table"
cslop migrate:run
cslop migrate:rollback

Common Patterns 🚧 WIP

Soft Deletes 🚧 WIP

// Instead of deleting, mark as deleted
@users[123]!{deletedAt: now}

// Query only active records
@users?{deletedAt:null}

Timestamps

// Create
@users!{
name: $.body.name,
createdAt: now,
updatedAt: now
}

// Update
@users[123]!{
name: $.body.name,
updatedAt: now
}

Pagination Helper

paginate: (query, page, size) {
offset: page * size
items: query[:size:offset]
total: query.count
{items, total, page, pages: total/size}
}

// Usage
*/users > paginate(@users, $.query.page ?? 0, 20) > #json
search: (table, fields, query) {
// Build OR conditions for multiple fields
conditions: fields >> {[_]:~query}
table?conditions
}

// Usage
*/search > search(@products, [name, description], $.query.q) > #json

Performance Tips

  1. Use indexes on frequently queried columns
  2. Select specific fields instead of returning all columns
  3. Limit results with pagination
  4. Use joins instead of multiple queries
  5. Cache frequent queries when data doesn't change often
  6. Use transactions for multi-step operations

Error Handling

// Catch not found
@users[$.id] >| #404

// Catch unique constraint violation
@users!{email:$.body.email} >| {
UniqueViolation: #400("email already exists")
_: #500
}

// Validate before insert
*/users + {
@users?{email:$.body.email}[0] ? #400("email exists") : _
@users!$.body > #201
}

Next Steps