Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,20 @@ data class User(
@FK val city: City
) : Entity<Int>

// DSL—query nested properties like city.name in one go
val users = orm.findAll { User_.city.name eq "Sunnyvale" }
// Type-safe predicates — query nested properties like city.name in one go
val users = orm.findAll(User_.city.name eq "Sunnyvale")

// Custom repositoryinherits all CRUD operations, add your own queries
// Custom repositoryinherits all CRUD operations, add your own queries
interface UserRepository : EntityRepository<User, Int> {
fun findByCityName(name: String) = findAll { User_.city.name eq name }
fun findByCityName(name: String) = findAll(User_.city.name eq name)
}
val users = userRepository.findByCityName("Sunnyvale")

// Query Builder for more complex operations
val users = orm.entity(User::class)
.select()
.where(User_.city.name eq "Sunnyvale")
.orderBy(User_.name)
.resultList
// Block DSL — build queries with where, orderBy, joins, pagination
val users = userRepository.select {
where(User_.city.name eq "Sunnyvale")
orderBy(User_.name)
}.resultList

// SQL Template for full control; parameterized by default, SQL injection safe
val users = orm.query { """
Expand All @@ -85,7 +84,7 @@ Full coroutine support with `Flow` for streaming and programmatic transactions:

```kotlin
// Streaming with Flow
val users: Flow<User> = orm.entity(User::class).selectAll()
val users: Flow<User> = orm.entity(User::class).select().resultFlow
users.collect { user -> println(user.name) }

// Programmatic transactions
Expand Down
6 changes: 3 additions & 3 deletions docs/common-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ data class OrderWithItems(

fun findOrderWithItems(orderId: Long): OrderWithItems? {
val order = orm.entity(Order::class).findById(orderId) ?: return null
val lineItems = orm.entity(LineItem::class).findAll { LineItem_.order eq order }
val lineItems = orm.entity(LineItem::class).findAll(LineItem_.order eq order)
return OrderWithItems(order, lineItems)
}
```
Expand Down Expand Up @@ -261,11 +261,11 @@ interface CustomerRepository : EntityRepository<Customer, Int> {

/** Find only non-deleted customers. */
fun findActive(): List<Customer> =
findAll { Customer_.deletedAt.isNull() }
findAll(Customer_.deletedAt.isNull())

/** Find a non-deleted customer by ID. */
fun findActiveOrNull(customerId: Int): Customer? =
find { (Customer_.id eq customerId) and Customer_.deletedAt.isNull() }
find((Customer_.id eq customerId) and Customer_.deletedAt.isNull())

/** Soft-delete a customer by setting the deletedAt timestamp. */
fun softDelete(customer: Customer): Customer {
Expand Down
20 changes: 10 additions & 10 deletions docs/dirty-checking.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ The selected update mode controls:
In `OFF` mode, Storm bypasses dirty checking entirely. Every call to `update()` generates a full UPDATE statement that writes all columns, regardless of whether values have actually changed.

```kotlin
val user = orm.find { User_.id eq 1 }
val user = orm.find(User_.id eq 1)
val updatedUser = user.copy(name = "New Name")

// Always generates: UPDATE user SET email=?, name=?, city_id=? WHERE id=?
Expand Down Expand Up @@ -144,7 +144,7 @@ userRepository.update(users.map { processUser(it) })
- If **any field changed**: A full-row UPDATE is executed (all columns are written)

```kotlin
val user = orm.get { User_.id eq 1 } // Storm observes: {id=1, email="a@b.com", name="Alice"}
val user = orm.get(User_.id eq 1) // Storm observes: {id=1, email="a@b.com", name="Alice"}

// Scenario 1: No changes
orm update user // No SQL executed - entity unchanged
Expand All @@ -163,7 +163,7 @@ When multiple entities of the same type are updated in a transaction, JDBC can b

```kotlin
// All updates have identical SQL shape - JDBC batches them
val users = userRepository.findAll { User_.city eq city }
val users = userRepository.findAll(User_.city eq city)
userRepository.update(users.map { it.copy(lastLogin = now()) })
```

Expand All @@ -174,7 +174,7 @@ userRepository.update(users.map { it.copy(lastLogin = now()) })
**Read-modify-write patterns:** When you load an entity and pass it back to update without modifications, ENTITY mode skips the UPDATE entirely.

```kotlin
val user = orm.get { User_.id eq userId }
val user = orm.get(User_.id eq userId)

// No changes made - UPDATE is skipped
orm update user
Expand All @@ -201,7 +201,7 @@ orm update updated
`FIELD` mode provides the most granular control. Storm compares each field individually and generates UPDATE statements that include only the columns that actually changed. Like ENTITY mode, if no fields changed, Storm skips the UPDATE entirely.

```kotlin
val user = orm.get { User_.id eq 1 } // {id=1, email="a@b.com", name="Alice", bio="...", settings="..."}
val user = orm.get(User_.id eq 1) // {id=1, email="a@b.com", name="Alice", bio="...", settings="..."}

// Only name changed
val updated = user.copy(name = "Bob")
Expand All @@ -221,7 +221,7 @@ orm update updated2
```kotlin
// Article has 20 columns including large 'content' field
// But we're only updating the view count
val article = orm.find { Article_.id eq articleId }
val article = orm.find(Article_.id eq articleId)
orm update article.copy(viewCount = article.viewCount + 1)
// UPDATE article SET view_count=? WHERE id=?
// The large 'content' column is NOT written
Expand Down Expand Up @@ -561,7 +561,7 @@ Dirty checking determines *what to update*. Optimistic locking determines *wheth

```kotlin
// This does NOT prevent lost updates:
val user = orm.find { User_.id eq 1 }
val user = orm.find(User_.id eq 1)
// ... another transaction modifies the same user ...
orm update user.copy(name = "New Name") // May overwrite other transaction's changes!

Expand All @@ -582,8 +582,8 @@ orm update user.copy(name = "New Name")
Storm tracks which entity types are affected by each mutation so it can selectively invalidate observed state. For template-based updates (using `orm update entity`), Storm knows the entity type and only clears observed state of that type. However, when you execute raw SQL mutations without entity type information, Storm cannot determine which entities may have been affected. Rather than risk stale comparisons that could silently skip a necessary UPDATE, Storm clears all observed state in the current transaction:

```kotlin
val user = orm.get { User_.id eq 1 } // Storm observes User state
val city = orm.get { City_.id eq 100 } // Storm observes City state
val user = orm.get(User_.id eq 1) // Storm observes User state
val city = orm.get(City_.id eq 100) // Storm observes City state

// Raw SQL mutation - Storm clears all observed state
orm.execute("UPDATE user SET name = 'Changed' WHERE id = 1")
Expand All @@ -608,7 +608,7 @@ data class User(
@Embedded val address: Address
) : Entity<Int>

val user = orm.find { User_.id eq 1 }
val user = orm.find(User_.id eq 1)
// With FIELD mode: only changed columns in Address are updated
orm update user.copy(address = user.address.copy(city = "New City"))
// UPDATE user SET city=? WHERE id=?
Expand Down
4 changes: 2 additions & 2 deletions docs/entity-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ Even without transaction-level caching, Storm preserves entity identity within a

```kotlin
// Even at READ_COMMITTED in a read-write transaction:
val orders = orderRepository.findAll { Order_.status eq "pending" }
val orders = orderRepository.findAll(Order_.status eq "pending")

// If order1 and order2 have the same customer, they share the instance
val customer1 = orders[0].customer.fetch()
Expand Down Expand Up @@ -355,7 +355,7 @@ When navigating relationships, `Ref.fetch()` automatically uses the cache:
```kotlin
transaction {
// Load orders with their users
val orders = orderRepository.findAll { Order_.status eq "pending" }
val orders = orderRepository.findAll(Order_.status eq "pending")

// If multiple orders share the same user, only one query per unique user
orders.forEach { order ->
Expand Down
2 changes: 1 addition & 1 deletion docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ Storm does not store collections on entities. This is intentional: collection fi

```kotlin
// Instead of user.orders (not supported)
val orders = orm.findAll { Order_.user eq user }
val orders = orm.findAll(Order_.user eq user)
```

### Why doesn't Storm support lazy loading?
Expand Down
2 changes: 1 addition & 1 deletion docs/first-entity.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ The `insertAndFetch` method sends an INSERT statement, retrieves the auto-genera
val user: User? = orm.entity<User>().findById(userId)

// Find by field value using the metamodel (requires storm-metamodel-processor)
val user: User? = orm.find { User_.email eq "alice@example.com" }
val user: User? = orm.find(User_.email eq "alice@example.com")
```

</TabItem>
Expand Down
10 changes: 5 additions & 5 deletions docs/first-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ The simplest way to query is with predicate methods directly on the ORM template
val users = orm.entity(User::class)

// Find all users in a city
val usersInCity: List<User> = users.findAll { User_.city eq city }
val usersInCity: List<User> = users.findAll(User_.city eq city)

// Find a single user by email
val user: User? = users.find { User_.email eq "alice@example.com" }
val user: User? = users.find(User_.email eq "alice@example.com")

// Combine conditions with and / or
val results: List<User> = users.findAll {
val results: List<User> = users.findAll(
(User_.city eq city) and (User_.name like "A%")
}
)

// Check existence
val exists: Boolean = users.existsById(userId)
Expand Down Expand Up @@ -78,7 +78,7 @@ For domain-specific queries that you will reuse, define a custom repository inte
interface UserRepository : EntityRepository<User, Int> {

fun findByEmail(email: String): User? =
find { User_.email eq email }
find(User_.email eq email)

fun findByNameInCity(name: String, city: City): List<User> =
findAll((User_.city eq city) and (User_.name eq name))
Expand Down
2 changes: 1 addition & 1 deletion docs/hydration.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ When a cache hit occurs, Storm skips the entire construction process for that en

```kotlin
// Query returns 1000 users, but only 50 unique cities
val users = userRepository.findAll { User_.city eq city }
val users = userRepository.findAll(User_.city eq city)
```

Without early lookup, Storm would construct 1000 `City` objects and then deduplicate. With early lookup:
Expand Down
19 changes: 9 additions & 10 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,20 @@ data class User(
@FK val city: City
) : Entity<Int>

// DSL—query nested properties like city.name in one go
val users = orm.findAll { User_.city.name eq "Sunnyvale" }
// Type-safe predicates — query nested properties like city.name in one go
val users = orm.findAll(User_.city.name eq "Sunnyvale")

// Custom repositoryinherits all CRUD operations, add your own queries
// Custom repositoryinherits all CRUD operations, add your own queries
interface UserRepository : EntityRepository<User, Int> {
fun findByCityName(name: String) = findAll { User_.city.name eq name }
fun findByCityName(name: String) = findAll(User_.city.name eq name)
}
val users = userRepository.findByCityName("Sunnyvale")

// Query Builder for more complex operations
val users = orm.entity(User::class)
.select()
.where(User_.city.name eq "Sunnyvale")
.orderBy(User_.name)
.resultList
// Block DSL — build queries with where, orderBy, joins, pagination
val users = userRepository.select {
where(User_.city.name eq "Sunnyvale")
orderBy(User_.name)
}.resultList

// SQL Template for full control; parameterized by default, SQL injection safe
val users = orm.query { """
Expand Down
2 changes: 1 addition & 1 deletion docs/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ Query separately:
val users = orm.findAll<User>()

// Batch fetch roles and group by user
val rolesByUser = orm.findAll { UserRole_.user inList users }
val rolesByUser = orm.findAll(UserRole_.user inList users)
.groupBy({ it.user }, { it.role })
```

Expand Down
4 changes: 2 additions & 2 deletions docs/metamodel.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ Once the metamodel is generated, you use the `_` suffixed classes in place of st

```kotlin
// Type-safe field reference
val users = orm.findAll { User_.email eq email }
val users = orm.findAll(User_.email eq email)

// Type-safe access to nested fields throughout the entire entity graph
val users = orm.findAll { User_.city.country.code eq "US" }
val users = orm.findAll(User_.city.country.code eq "US")

// Multiple conditions
val users = orm.entity(User::class)
Expand Down
18 changes: 9 additions & 9 deletions docs/migration-from-jpa.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public interface UserRepository extends JpaRepository<User, Long> {
interface UserRepository : EntityRepository<User, Long> {

fun findByEmail(email: String): User? =
find { User_.email eq email }
find(User_.email eq email)

fun findByCity(city: City): List<User> =
select()
Expand All @@ -122,7 +122,7 @@ interface UserRepository : EntityRepository<User, Long> {
.resultList

fun findRecentUsers(since: LocalDateTime): List<User> =
findAll { User_.createdAt gt since }
findAll(User_.createdAt gt since)
}
```

Expand Down Expand Up @@ -195,10 +195,10 @@ public interface UserRepository extends JpaRepository<User, Long> {
interface UserRepository : EntityRepository<User, Long> {

fun findByEmail(email: String): User? =
find { User_.email eq email }
find(User_.email eq email)

fun findByCity(city: City): List<User> =
findAll { User_.city eq city }
findAll(User_.city eq city)
}
```

Expand Down Expand Up @@ -243,7 +243,7 @@ Optional<User> findByEmail(@Param("email") String email);
```kotlin
// Storm
fun findByEmail(email: String): User? =
find { User_.email eq email }
find(User_.email eq email)
```

</TabItem>
Expand Down Expand Up @@ -281,7 +281,7 @@ return em.createQuery(cq).getResultList();

```kotlin
// Storm
orm.findAll { User_.city eq city }
orm.findAll(User_.city eq city)
```

</TabItem>
Expand Down Expand Up @@ -355,7 +355,7 @@ private List<Order> orders;

Storm approach (query the "many" side):
```kotlin
val orders = orm.findAll { Order_.user eq user }
val orders = orm.findAll(Order_.user eq user)
```

## Transaction Migration
Expand Down Expand Up @@ -504,7 +504,7 @@ data class CustomerProfile(
// Storm repository (new)
interface CustomerProfileRepository : EntityRepository<CustomerProfile, Long> {
fun findByCustomerId(customerId: Long): CustomerProfile? =
find { CustomerProfile_.customerId eq customerId }
find(CustomerProfile_.customerId eq customerId)
}

// Service that uses both
Expand Down Expand Up @@ -642,7 +642,7 @@ Storm intentionally does not support collection fields on entities. This is a de
val orders = user.orders // Not supported

// Right approach
val orders = orm.findAll { Order_.user eq user }
val orders = orm.findAll(Order_.user eq user)
```

## Schema Validation
Expand Down
Loading
Loading