Skip to content
Open
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
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
// The Storm plugin imports the BOM, adds storm-java21 and storm-core, wires the
// metamodel annotation processor, and enables the preview flags that Storm's Java
// String Templates (JEP 430) require on compile, test, and run (BootRun included).
id("st.orm") version "1.13.1"
id("st.orm") version "1.14.0"
}

group = "st.orm.demo"
Expand All @@ -19,6 +19,8 @@ java {
}

repositories {
// TEMPORARY: resolves Storm 1.14.0 from a local build. Remove once it is on Maven Central.
mavenLocal()
mavenCentral()
}

Expand Down
9 changes: 9 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
// TEMPORARY: mavenLocal() resolves the st.orm plugin from a local Storm build.
// Remove this pluginManagement block once 1.14.0 is on the Gradle Plugin Portal.
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}

rootProject.name = "storm-imdb-demo"
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ default List<Genre> findAllOrderedByName() {
default List<GenreMovieCount> findGenresWithMovieCounts() {
return select(GenreMovieCount.class, RAW."\{Genre.class}, COUNT(*)")
.innerJoin(MovieGenre.class).on(Genre.class)
.groupBy(Genre_.id, Genre_.name)
.groupBy(Genre_.id)
.orderBy(Genre_.name)
.getResultList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import java.util.List;
import st.orm.demo.imdb.model.Genre;
import st.orm.demo.imdb.model.Genre_;
import st.orm.demo.imdb.model.Movie;
import st.orm.demo.imdb.model.MovieGenre;
import st.orm.demo.imdb.model.MovieGenrePk;
Expand All @@ -19,7 +18,7 @@ public interface MovieGenreRepository extends EntityRepository<MovieGenre, Movie
default List<Genre> findGenres(Movie movie) {
return select(Genre.class)
.where(MovieGenre_.movie, movie)
.orderByAny(Genre_.name)
.orderBy(MovieGenre_.genre.name)
.getResultList();
}

Expand All @@ -31,7 +30,7 @@ default List<Genre> findGenres(Movie movie) {
default List<GenreRatingStatistics> findGenreRatingStatistics(int minimumMovieCount, int limit) {
return select(GenreRatingStatistics.class, RAW."\{Genre.class}, AVG(\{Rating_.averageRating}), COUNT(*)")
.innerJoin(Rating.class).on(Movie.class)
.groupByAny(Genre_.id, Genre_.name)
.groupBy(MovieGenre_.genre)
.having(RAW."COUNT(*) >= \{minimumMovieCount}")
.orderByDescending(RAW."AVG(\{Rating_.averageRating})")
.limit(limit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ default List<MovieSummary> findTitleSuggestions(String query, int limit) {
return select()
.innerJoin(Rating.class).on(MovieSummary.class)
.where(RAW."LOWER(\{MovieSummary_.primaryTitle}) LIKE LOWER(\{pattern})")
.orderByDescendingAny(Rating_.voteCount)
.orderByDescending(Rating_.voteCount)
.limit(limit)
.getResultList();
}
Expand All @@ -50,12 +50,15 @@ default List<MovieSummary> findTitleSuggestions(String query, int limit) {
* All movies in a genre with keyset scrolling. The junction table has a
* composite key and cannot be scrolled directly, so the scroll runs on
* the movie's simple primary key with a JOIN through the junction table,
* resolved automatically against the projection by table.
* resolved automatically against the projection by table. The scroll key
* has to identify one row of the projection, which a key on the junction
* table would not.
*/
default Window<MovieSummary> scrollByGenre(Genre genre, Scrollable<MovieSummary> scrollable) {
return select()
.innerJoin(MovieGenre.class).on(MovieSummary.class)
.whereAny(predicate -> predicate.whereAny(MovieGenre_.genre, genre))
.where(MovieGenre_.genre, genre)
.narrow(MovieSummary.class)
.scroll(scrollable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

import java.util.List;
import st.orm.demo.imdb.model.Movie;
import st.orm.demo.imdb.model.Movie_;
import st.orm.demo.imdb.model.Person;
import st.orm.demo.imdb.model.Person_;
import st.orm.demo.imdb.model.Principal;
import st.orm.demo.imdb.model.PrincipalPk;
import st.orm.demo.imdb.model.Principal_;
Expand All @@ -35,7 +33,7 @@ default List<FilmographyEntry> findFilmography(Person person) {
return select(FilmographyEntry.class, RAW."\{Principal.class}, \{Movie.class}, \{Rating_.averageRating}")
.innerJoin(Rating.class).on(Movie.class)
.where(Principal_.person, person)
.orderByDescendingAny(Rating_.averageRating)
.orderByDescending(Rating_.averageRating)
.getResultList();
}

Expand All @@ -56,7 +54,7 @@ default List<RelatedMovie> findMoviesSharingCast(List<Person> castMembers, Movie
return select(RelatedMovie.class, RAW."\{Movie.class}, COUNT(*)")
.where(predicate -> predicate.where(Principal_.person, IN, castMembers)
.and(predicate.where(Principal_.movie, NOT_EQUALS, excludedMovie)))
.groupByAny(Movie_.id, Movie_.primaryTitle, Movie_.originalTitle, Movie_.startYear, Movie_.runtimeMinutes)
.groupBy(Principal_.movie)
.orderByDescending(RAW."COUNT(*)")
.limit(limit)
.getResultList();
Expand All @@ -66,7 +64,7 @@ default List<RelatedMovie> findMoviesSharingCast(List<Person> castMembers, Movie
default List<ProlificActor> findMostProlificActors(int limit) {
return select(ProlificActor.class, RAW."\{Person.class}, COUNT(*)")
.where(Principal_.category, IN, List.of("actor", "actress"))
.groupByAny(Person_.id, Person_.primaryName, Person_.birthYear, Person_.deathYear)
.groupBy(Principal_.person)
.orderByDescending(RAW."COUNT(*)")
.limit(limit)
.getResultList();
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/st/orm/demo/imdb/repository/RatingRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static st.orm.Operator.IS_NOT_NULL;

import java.util.List;
import st.orm.Data;
import st.orm.demo.imdb.model.Genre;
import st.orm.demo.imdb.model.Movie;
import st.orm.demo.imdb.model.MovieGenre;
Expand Down Expand Up @@ -36,17 +37,18 @@ default List<Rating> findTopRated(int minimumVoteCount, int limit) {
* builder is assembled.
*/
default List<Rating> findTopMovies(Genre genre, TopMoviesSort sortBy, int minimumVoteCount, int limit) {
QueryBuilder<Rating, Rating, Movie> query = select()
.where(Rating_.voteCount, GREATER_THAN_OR_EQUAL, minimumVoteCount);
QueryBuilder<Data, Rating, Movie> query = select()
.where(Rating_.voteCount, GREATER_THAN_OR_EQUAL, minimumVoteCount)
.widen();
if (genre != null) {
query = query.innerJoin(MovieGenre.class).on(Movie.class)
.whereAny(predicate -> predicate.whereAny(MovieGenre_.genre, genre));
.where(MovieGenre_.genre, genre);
}
query = switch (sortBy) {
case RATING -> query.orderByDescending(Rating_.averageRating);
case YEAR -> query
.whereAny(predicate -> predicate.whereAny(Movie_.startYear, IS_NOT_NULL))
.orderByDescendingAny(Movie_.startYear, Rating_.averageRating);
.where(Movie_.startYear, IS_NOT_NULL)
.orderByDescending(Movie_.startYear, Rating_.averageRating);
};
return query.limit(limit).getResultList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ void findRecentViewsStaysOnTheViewTableThanksToRef(ORMTemplate orm, SqlCapture c
@Test
void recordingAViewInsertsByIdWithoutLoadingTheMovie(ORMTemplate orm, SqlCapture capture) {
MovieViewRepository movieViewRepository = orm.repository(MovieViewRepository.class);
capture.run(() ->
capture.record(() ->
movieViewRepository.insert(
// Older than the seeded views so it never becomes the newest.
new MovieView(0L, Ref.of(Movie.class, "tt0110912"), Instant.parse("2026-06-30T00:00:00Z"))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ void aGalleryRoundTripsItsPhotosThroughTheJsonColumn(ORMTemplate orm, SqlCapture
new Photo("https://upload.wikimedia.org/keanu-2.jpg")
);

capture.run(() -> {
capture.record(() -> {
galleryRepository.insert(new PersonGallery(keanu, photos, Instant.parse("2026-07-03T10:00:00Z")));
assertEquals(photos, galleryRepository.getById(keanu).photos());
});
Expand All @@ -40,8 +40,8 @@ void aGalleryRoundTripsItsPhotosThroughTheJsonColumn(ORMTemplate orm, SqlCapture
void aRefreshedGalleryReplacesTheStoredPhotos(ORMTemplate orm) {
PersonRepository personRepository = orm.repository(PersonRepository.class);
PersonGalleryRepository galleryRepository = orm.repository(PersonGalleryRepository.class);
// Morgan Freeman is not touched by other tests in this class — the
// @StormTest database is shared across the class's test methods.
// @StormTest rolls each test back, so this person has no gallery yet
// however the class orders its methods.
Ref<Person> morgan = Ref.of(personRepository.getById("nm0000151"));

// The refresh runs the way the service does it: upsert writes the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ class WatchlistRepositoryTest {
void theToggleCycleExistsInsertExistsRemoveWorksOnTheMovieKey(ORMTemplate orm, SqlCapture capture) {
MovieRepository movieRepository = orm.repository(MovieRepository.class);
WatchlistRepository watchlistRepository = orm.repository(WatchlistRepository.class);
// Pulp Fiction is not touched by other tests in this class — the
// @StormTest database is shared across the class's test methods.
// @StormTest rolls each test back, so the watchlist starts out empty
// however the class orders its methods.
Movie pulpFiction = movieRepository.getById("tt0110912");

capture.run(() -> {
capture.record(() -> {
assertFalse(watchlistRepository.existsById(pulpFiction));

watchlistRepository.insert(new Watchlist(pulpFiction, Instant.now()));
Expand Down