angelxmoreno / angelxmoreno/bun-sqlite-orm
[Feature]: Add static update methods for efficient single-query updates
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
## Problem Statement
Currently, bun-sqlite-orm only supports updates through instance methods, forcing an inefficient \"fetch-then-save\" pattern:
```typescript
// Current: Requires 2 queries (SELECT + UPDATE)
const user = await User.get(1);
user.name = 'Updated Name';
user.email = 'new@example.com';
await user.save();
```
This approach has several problems:
1. **Performance**: Requires 2 database queries instead of 1
2. **Memory overhead**: Must load entire entity just to update a few fields
3. **Race conditions**: Data could change between fetch and save
4. **Verbosity**: Simple updates require multiple lines of code
5. **API inconsistency**: Static methods exist for reads but not writes
## Proposed Solution
Add static update methods to `BaseEntity` that perform direct database updates without requiring entity instantiation.
### Method Signatures
```typescript
// Update records matching conditions
static async update(
this: new () => T,
conditions: Where,
data: Partial
): Promise
// Convenience method for updating by ID
static async updateById(
this: new () => T,
id: number | string,
data: Partial
): Promise
// Insert if not exists, update if exists
static async upsert(
this: new () => T,
conditions: Where,
data: Partial
): Promise
```
### Design Decisions
**Return Values**: All methods return `number` (count of affected rows)
- Consistent API across all update methods
- Efficient - no need to fetch updated entities
- Clear indication of operation success (0 = no matches, >0 = updated)
**Validation**: Run same validation as instance saves
- Maintains data integrity
- Consistent behavior with existing save operations
- Validation errors thrown before database operation
**Undefined Handling**: `undefined` values treated as `null`
- Clear, predictable behavior
- Matches SQL null semantics
- No ambiguity about field updates
**Error Handling**:
- No matching records: Return `0` (not an error)
- Constraint violations: Throw `ConstraintViolationError`
- Validation failures: Throw `ValidationError`
## Example Usage
### Basic Updates
```typescript
// Update single field by ID
const count = await User.updateById(1, { name: 'New Name' });
console.log(`Updated ${count} user(s)`); // \"Updated 1 user(s)\" or \"Updated 0 user(s)\"
// Update multiple fields
await User.updateById(1, {
name: 'John Doe',
email: 'john@example.com',
lastLoginAt: new Date()
});
```
### Conditional Updates
```typescript
// Update by conditions
const count = await User.update(
{ status: 'pending' },
{ status: 'approved', approvedAt: new Date() }
);
console.log(`Approved ${count} pending users`);
// Complex conditions (when issue #35 is complete)
await User.update(
{
createdAt: { lt: thirtyDaysAgo },
status: { in: ['pending', 'draft'] }
},
{ status: 'archived' }
);
```
### Upsert Operations
```typescript
// Insert or update user settings
await UserSettings.upsert(
{ userId: 123 },
{ theme: 'dark', notifications: true, language: 'en' }
);
// Upsert with complex data
await Analytics.upsert(
{ userId: 456, date: today },
{ pageViews: 1, sessionDuration: 300 }
);
```
### API Endpoints
```typescript
// RESTful PATCH endpoint
app.patch('/api/users/:id', async (req, res) => {
const count = await User.updateById(req.params.id, req.body);
if (count === 0) {
return res.status(404).json({ error: 'User not found' });
}
res.json({ message: 'User updated successfully' });
});
// Bulk update endpoint
app.patch('/api/users/bulk', async (req, res) => {
const { conditions, data } = req.body;
const count = await User.update(conditions, data);
res.json({
message: `Updated ${count} users`,
affectedRows: count
});
});
```
## Implementation Details
### SQL Generation
```typescript
// update() method generates:
UPDATE users
SET name = ?, email = ?, updated_at = ?
WHERE status = ?
// updateById() method generates:
UPDATE users
SET name = ?, email = ?, updated_at = ?
WHERE id = ?
// upsert() method generates:
INSERT INTO users (user_id, theme, notifications)
VALUES (?, ?, ?)
ON CONFLICT (user_id)
DO UPDATE SET theme = excluded.theme, notifications = excluded.notifications
```
### Validation Integration
```typescript
// Before SQL execution, validate the data partial
const entity = new this();
Object.assign(entity, data);
await entity.validate(); // Throws ValidationError if invalid
```
### Error Handling
```typescript
try {
const count = await User.updateById(1, { email: 'invalid-email' });
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation errors
console.log('Validation failed:', error.errors);
} else if (error instanceof ConstraintViolationError) {
// Handle database constraint violations
console.log('Constraint violation:', error.constraintType);
}
}
```
## Performance Benefits
### Before (Instance Method)
```typescript
// 2 database queries + memory overhead
const user = await User.get(1); // SELECT query
user.name = 'Updated Name';
await user.save(); // UPDATE query
```
### After (Static Method)
```typescript
// 1 database query, no memory overhead
await User.updateById(1, { name: 'Updated Name' }); // UPDATE query only
```
### Bulk Operations
```typescript
// Before: N+2 queries for N records
const users = await User.find({ status: 'pending' }); // 1 SELECT
for (const user of users) {
user.status = 'approved';
await user.save(); // N UPDATE queries
}
// After: 1 query regardless of record count
await User.update({ status: 'pending' }, { status: 'approved' }); // 1 UPDATE
```
## Dependencies
- **Issue #35** - Advanced WHERE conditions: Will improve `conditions` parameter typing
- **Issue #57** - Enhanced error system: Will provide proper error types (`ConstraintViolationError`, etc.)
## Breaking Changes
None - these are new method additions.
## Alternatives Considered
1. **Return updated entities**: Would require additional SELECT query, defeating performance purpose
2. **Skip validation**: Would create inconsistency with instance save behavior
3. **Separate bulk methods**: `update()` already handles bulk operations efficiently
4. **Different naming**: Considered `modify()`, `patch()`, but `update()` is more standard
## Additional Context
### Integration with Existing Features
- Uses existing validation system for data integrity
- Integrates with enhanced error system (issue #57)
- Will benefit from typed conditions (issue #35)
- Follows same patterns as existing static methods
### Future Enhancements
- Entity lifecycle hooks (when implemented)
- Query result caching integration
- Batch update optimizations
- Transaction support integration
This feature addresses a major gap in the current API and would significantly improve performance for update operations while maintaining consistency with the existing codebase design.
Contributor guide
Research direction
Start at BaseEntity and compare the proposed static update methods with the existing static read methods, then trace instance-save validation and SQL generation. Review dependencies on issues #35 and #57. Done means update, updateById, and upsert provide the stated return values, validation, undefined handling, and error behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun, sqlite, typescript
- Domain
- backend, database
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100