java-topology/docs/tickets/exposed-0003-Table-clone-consParamNames-O2.md
russell@unturf.com 547a9f5738 ORM wave 2: 10 new defects — Active Record +3, Exposed +3, SeaORM +4 (167 sites, 64 ecosystems)
rails-0009: FilterAttributeHandler filter_parameters Array O(A×F) → Set (450×)
rails-0010: Encryption::AutoFilteredParameters two Array scans → Set (250×)
rails-0011: TimeZoneConversion skip_list Array O(M×C×S) → Set (20×)

exposed-0001: SchemaUtilityApi mapMissingColumnStatements O(N×M) → map (118×)
exposed-0002: IdentifierManagerApi isAKeyword O(K) linear → HashSet (144×)
exposed-0003: Table.clone consParams.map fresh List → hoisted HashSet (6×)

seaorm-0001: active_model establish_links leftover.any O(N²) → HashSet (501×)
seaorm-0002: rbac engine group_permissions .values().find() → HashMap by ID (502×)
seaorm-0003: schema builder sorted_tables Vec::contains → HashSet (500×)
seaorm-0004: TopologicalSort from_iter seen Vec O(N²) → BTreeSet (28×)

Unit tests: RailsTest 11/11, ExposedTest 3/3, SeaORMTest 4/4 PASS
Whitepaper: 157→167 sites, 62→64 ecosystems; §13.12 ORM Wave 2 added
2026-03-27 13:49:46 -04:00

52 lines
2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# exposed-0003: Table.clone — O(N²) repeated List allocation in property filter
**Severity:** MEDIUM
**File:** exposed-core/src/main/kotlin/org/jetbrains/exposed/v1/core/Table.kt
**Line:** 1686
**Status:** PATCHED
## Description
The private `T.clone()` utility in `Table` is used internally when cloning column objects (e.g., during
alias creation and column type mutation). It contains:
```kotlin
val allValues = memberProperties
.filter { it in mutableProperties || it.name in consParams.map(KParameter::name) }
.associate { it.name to (replaceArgs[it] ?: it.get(this@clone)) }
```
The predicate `it.name in consParams.map(KParameter::name)` is evaluated for every element of
`memberProperties`. Each evaluation calls `.map(KParameter::name)`, allocating a fresh `List<String?>`.
For a class with P properties and C constructor parameters, this is P × C string comparisons plus P list
allocations.
`Column` classes can have 1020 properties; this is called once per column per alias/clone operation. In a
query with 50 aliased columns this function runs 50 times, each time performing up to 400 string comparisons
with 20 temporary `List` allocations per call — 20 000 comparisons and 1 000 heap allocations total.
## Root Cause
`consParams.map(KParameter::name)` is a lambda-captured expression inside the `.filter {}` predicate.
Kotlin does not hoist it; it runs inside the hot loop.
## Fix
Pre-compute the parameter name set once, outside the filter:
```kotlin
// Before (O(P×C) with P list allocations):
val allValues = memberProperties
.filter { it in mutableProperties || it.name in consParams.map(KParameter::name) }
.associate { it.name to (replaceArgs[it] ?: it.get(this@clone)) }
// After (O(P) with O(1) lookup):
val consParamNames = consParams.mapTo(HashSet()) { it.name }
val allValues = memberProperties
.filter { it in mutableProperties || it.name in consParamNames }
.associate { it.name to (replaceArgs[it] ?: it.get(this@clone)) }
```
## Speedup
~15× at P=20 properties, C=15 constructor parameters.