Help Shape the Quick 13 Release

Quick 13 has reached beta, bringing a substantial collection of new persistence, relationship, testing, and performance capabilities—and now it needs some real-world mileage.

New Features and Improvements

Highlights include:

  • Automatic timestamps and built-in soft deletes
  • New upsert(), createAll(), replicate(), and touch() APIs
  • Laravel-inspired model factories for testing
  • Richer belongsToMany pivot models
  • New relationship helpers such as whereBelongsTo() and whereHasValue()
  • Improved entity state tracking, lifecycle hooks, and refresh-on-save support
  • Better compatibility across Lucee, Adobe ColdFusion, and BoxLang
  • An upgrade to qb 14

Performance Improvements

Quick 13 reduces repeated metadata, hydration, and attribute-state work on several frequently used code paths:

  • Qualified-column caching reduced qualification calls from 6 to 3—a 50% reduction in the regression fixture.
  • Hydration now resolves attribute bindings once per result set instead of once per row. For a 1,000-row result, that eliminates 999 repeated resolution passes.
  • A bounded, process-local entity definition registry reduces repeated metadata compilation across instances of the same entity.
  • Additional optimizations reduce allocations during attribute tracking, refresh queries, qualified-column lookups, and runtime attribute overlays.

Focused microbenchmarks measured the following improvements over Quick 12.0.12:

Operation Lucee 6.2.8.20 Adobe ColdFusion 2021.0.23 BoxLang 1.17.0
Entity creation 39.1% faster 28.1% faster 36.0% faster
Single-entity hydration 26.9% faster 31.5% faster 38.2% faster
Hydrating 100 entities 7.1% faster 36.6% faster 44.3% faster
Hydrating 1,000 entities 11.3% faster 42.5% faster 27.2% faster
hasMany relationship construction 64.1% faster 24.6% faster 26.3% faster

Each result is the median elapsed time from 15 samples of 50 iterations after warm-up. These figures describe reductions in repeated internal work, not equivalent reductions in total application request time. Actual improvements will vary by CFML engine, JVM, entity definition, and query shape.

Breaking Changes

As a major release, Quick 13 includes several behavior changes to review while testing:

  • Quick now requires qb 14.
  • Automatic timestamps are enabled by default for declared createdDate and modifiedDate attributes.
  • reset() now clears the cached query builder along with entity and relationship state.
  • Loaded primary keys are now immutable.
  • Duplicate entity property names now throw QuickDuplicateProperty during initialization.
  • Custom casts now receive null values, and BooleanCast@quick preserves null instead of converting it to false.
  • Relationship getters on new entities now return null, a configured default, or an empty collection without querying the database.
  • The second positional argument to appendVirtualAttribute() is now defaultValue; excludeFromMemento has moved to the third argument.

See the Quick 13 Upgrade Guide for examples and upgrade instructions.

We Need You (And Your Apps)

Install the beta, check out the docs, and let us know how it runs for you. Testing existing applications, large hydration workloads, relationship-heavy queries, and custom casts would be especially valuable!

@elpete, I’m your huckleberry. I’ll test this on a bunch of apps I’m actively developing and about 10 others in production that use Quick. It’s my go-to these days.

One feature I’ve been wishing for in Quick for ages is the ability to populate relationships on a new entity before the root entity has been persisted.

For example, I’d love to be able to do this:

post = getInstance( "Post" );

post.fill( {
    title : "Foo"
} );

post.setCategories( rc.categories );

validateOrFail( post );

post.save();

Today, setCategories() needs the Post to already exist because Quick needs its primary key to update the relationship. That means I end up having to save the Post first, populate its relationships, validate it, and wrap the whole thing in a transaction so I can roll everything back if validation fails.

I’ve never loved that pattern because persistence mechanics end up dictating the order of my application logic. In particular, I don’t like having to persist an entity before I can fully populate and validate it.

What I’d love is for relationship setters on a new entity to update or stage the relationship in memory. Then, when save() inserts the root entity and obtains its primary key, Quick could persist any pending relationship changes afterward.

I’m not necessarily suggesting full object-graph persistence where Quick recursively figures out how to save an arbitrary tree of new entities. Even just supporting relationships to already-persisted entities or IDs would cover most of my use cases.

Conceptually, I’d like to be able to fully populate an entity, including its relationships, validate the complete entity, and only then tell Quick to persist it.

This also feels like a natural next step from issue #177, which I opened years ago around making relationships on new/non-loaded entities behave more like relationships on persisted entities. The more I’ve used Quick since then, the more I’ve come to feel that the persistence state of the root entity shouldn’t dictate how much of the entity graph I’m allowed to construct in memory.

I came across a ticket about this, maybe yours, when I was working on Quick 13. The truth is it has always been a design goal of Quick to not deal with object graph persistence in any way. I’d probably recommend writing a module or a fork of Quick to work the way you want. Sorry, and best of luck.