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
13 changes: 13 additions & 0 deletions docs/tech-debt.md
Original file line number Diff line number Diff line change
Expand Up @@ -674,3 +674,16 @@ The rows endpoint is still the hard case. `RowsController` unit tests cover rout
**Where.** `Document::register_field_meta` in `includes/PostType/Document.php`.

**Solution.** Call `update_meta_cache( 'post', $field_ids )` once before the foreach so the subsequent `get_post_meta` calls are cache hits. The field-id list is already in memory from the preceding `get_posts`, so the warmup is a one-liner.
<a id="td-formula-materialized-values"></a>

**Formula values materialize synchronously.**

**What.** Formula output is stored in the same `field-<id>` row meta as normal fields. That keeps rows, exports, filters, sorts, and the field-value index reading one canonical value, but it means Cortext has to keep that stored value fresh. In v0, it does that synchronously: all formulas on a row after row writes, visible rows after list reads, and every row in the collection after a formula is created or edited. Volatile formulas such as `now()` get one extra refresh only when a request sorts or filters by that volatile formula, because SQL and the sidecar index read the materialized meta.

This is fine while collections are small and formulas are few. It becomes the wrong shape for large tables with several dependent formulas, or for sorting thousands of rows by something like `dateBetween(now(), field("Created"), "days")`. The code stores dependency ids already, but it does not yet use them as a dirty graph or background job plan.

**Where.** `includes/Formula/Materializer.php`, formula refresh calls in `includes/PostType/Document.php`, `includes/Rest/RowsController.php`, and `includes/Rest/FieldsController.php`, plus formula indexing in `includes/FieldValues/FieldValueIndex.php`.

**Solution.** For non-volatile formulas, keep materialized row meta as the source of truth and make refresh narrower. Row writes should recompute only formulas that depend on the changed field, in dependency order. Formula create/update can mark affected rows dirty and process them in batches instead of blocking the request on the whole collection.

For volatile formulas, treat materialized meta as a cache rather than the source of truth. Define explicit refresh points, with view load as the obvious baseline, and document the staleness contract. Once that exists, collection-wide recompute calls can shrink to those refresh points plus repair tools and migrations.
8 changes: 5 additions & 3 deletions includes/FieldValues/FieldValueIndex.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

defined( 'ABSPATH' ) || exit;

use Cortext\Fields\FieldTypeRegistry;
use Cortext\PostType\Document;
use Cortext\PostType\Field;
use Cortext\Taxonomy\TraitTaxonomy;
Expand Down Expand Up @@ -1069,13 +1070,14 @@ private function delete_field( int $field_id ): void {
}

private function index_rows_for_row_field( int $row_id, int $field_id, int $collection_id ): array {
$field_type = (string) get_post_meta( $field_id, 'type', true );
if ( '' === $field_type || 'rollup' === $field_type ) {
$raw_field_type = (string) get_post_meta( $field_id, 'type', true );
if ( '' === $raw_field_type || 'rollup' === $raw_field_type ) {
return array();
}

$field_type = FieldTypeRegistry::effective_type_for_field( $field_id, $raw_field_type );
$key = Relations::meta_key( $field_id );
$is_multiple = 'multiselect' === $field_type || ( 'relation' === $field_type && Relations::relation_is_multiple( $field_id ) );
$is_multiple = 'multiselect' === $raw_field_type || ( 'relation' === $raw_field_type && Relations::relation_is_multiple( $field_id ) );
$stored = get_post_meta( $row_id, $key, ! $is_multiple );
$post_status = (string) get_post_status( $row_id );
$rows = array();
Expand Down
24 changes: 24 additions & 0 deletions includes/Fields/FieldTypeRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ final class FieldTypeRegistry {
'wp_meta_type' => 'string',
'operators' => array(),
),
'formula' => array(
'sortable' => false,
'filterable' => false,
'text_like' => false,
'wp_meta_type' => 'string',
'operators' => array(),
),
);

/**
Expand Down Expand Up @@ -213,4 +220,21 @@ public static function capabilities_for( string $type ): array {
'operators' => self::operators_for( $type ),
);
}

public static function effective_type_for_field( int $field_id, string $type = '' ): string {
$field_type = '' !== $type ? $type : (string) get_post_meta( $field_id, 'type', true );
if ( 'formula' !== $field_type ) {
return $field_type;
}
$result_type = (string) get_post_meta( $field_id, 'formula_result_type', true );
return self::exists( $result_type ) && 'formula' !== $result_type ? $result_type : 'text';
}

public static function capabilities_for_field( int $field_id, string $type = '' ): array {
return self::capabilities_for( self::effective_type_for_field( $field_id, $type ) );
}

public static function wp_meta_type_for_field( int $field_id, string $type = '' ): string {
return self::wp_meta_type( self::effective_type_for_field( $field_id, $type ) );
}
}
Loading
Loading