Skip to content

Release/3.0.0 - WIP#147

Merged
JJJ merged 128 commits into
release/2.1.0from
release/3.0.0
May 28, 2026
Merged

Release/3.0.0 - WIP#147
JJJ merged 128 commits into
release/2.1.0from
release/3.0.0

Conversation

@JJJ
Copy link
Copy Markdown
Collaborator

@JJJ JJJ commented Jul 6, 2022

  • Traits
  • Parsers
  • Operators
  • PHP Unit Tests
  • Indexes

JJJ added 2 commits July 6, 2022 15:01
* Traits
* Parsers (yuck)
* Operators (yuck)

A bunch of things are broken here.
@JJJ JJJ changed the title First commit to 3.0.0 experimental: Release/3.0.0 - WIP Oct 31, 2025
JJJ added 11 commits May 11, 2026 18:30
Query: parse_where_parsers()
- Fix $args['table_alias'] -> $args['primary_alias']; 'table_alias' key
  does not exist, so every Meta/Date/Compare clause alias was null
- Uncomment and expand the $qv narrowing line; Meta, Date, and Compare
  parsers were receiving the full $query_vars blob instead of their own
  sub-array, producing no SQL for any meta/date/compare query
- Add is_array() guard to narrowing so Search stays on full $query_vars;
  without it, a scalar 'search' string would be passed as the query array
- Change Search parser 'default' from null to ''; null fed through to the
  random sentinel value, which meant every query ran a LIKE search for the
  sentinel string regardless of whether the user searched for anything

Meta: get_sql_for_clause()
- Remove early return $retval that blocked the entire method body
- Remove debug lines ($hello / var_dump)
- Fix hardcoded $db->postmeta -> $this->meta_table and post_ID ->
  $this->meta_column in the NOT EXISTS subquery builder; would have
  generated invalid SQL for any non-post object type
- Initialize $subquery_alias, $meta_compare_string_start, and
  $meta_compare_string_end to '' before the if ($neg) block
- Rename $operator to $regex_op in REGEXP/RLIKE cases to avoid shadowing
  the outer $operator object
- Remove dead $operator = $meta_compare_key assignment in NOT REGEXP case
- Move $retval definition before the get_db() bail so bail can return it
- Add missing if (empty($db)) guard
- Inline $_meta_type assignment using null coalescing operator
- Fix get_first_keys() to return the array directly (parameter was unused)
- Docblock: @param Query $caller -> @param \BerlinDB\Database\Query|null
- Docblock: @return string[] -> @return array with @type string[] per key
- Fix stale comments ("sooo", "Already exists?")

Compare: extend Base instead of Meta
- Compare::get_sql_for_clause() uses only trait/Base methods; it does not
  use $this->meta_table, $this->meta_column, or any other Meta property
- Inheriting Meta silently dragged in Meta::get_sql()'s _get_meta_table()
  lookup, which returns false for any custom table type, killing all
  compare queries with no error
- Change extends Meta -> extends Base; no other changes needed

By: fix copy-paste file docblock title ("In Query Var" -> "By Query Var")

Compare, Date: correct file-level @SInCE from 1.0.0 to 3.0.0; these
  classes are new in 3.0.0 and did not exist in earlier releases

NotIn: align file title to "Not In Query Var Parser Class" to match the
  naming convention of all other parser files
* Yoda conditions
* Whitespace
* Docs
* Update @SInCE tags to 3.0.0
Refactor and harden Schema item API, layout, and internals.

- Fix legacy schema hydration path in setup_items by correctly using item values
- Add and organize generic item API methods near add_item: add/get/has/set/remove for collection-level operations
- Keep column/index convenience wrappers grouped in Item Helpers
- Improve class structure with clearer section boundaries:
- Public Item Core, Private Internals, Item Helpers, Validators
- Add schema validation safeguards used by CREATE TABLE generation: duplicate names, missing names, unknown index columns, primary key conflicts
- Gate get_create_table_string() on schema validity and skip empty SQL fragments
- Introduce reusable create_item() helper to centralize item instantiation logic
- Reduce repeated work in setup and validation paths for cleaner/faster internals
- Preserve backward compatibility and public behavior while improving readability and maintainability
- Update copyright year to 2021-2026
- Expand class docblock to describe Column/Index collections, legacy
  hydration, validation, and SQL generation
- Add override note to $column and $index property docs
- Type $columns/$indexes as Column[]/Index[] and document legacy support
- Rewrite sunrise()/init() docs to describe Traits\Boot lifecycle role
- Add periods to all inline comments missing them
- Expand @param for clear() to document the empty-string-clears-all behavior
- Update all @return types to use concrete types (Column|false, Index[],
  string[], etc.) throughout public and private methods
- Document the 'primary' name alias in get_item(), remove_item(),
  get_index(), has_index(), and remove_index() docblocks
- Fix get_item_class() @return from "Class object" to class name string
- Rewrite get_items_create_string() @return to describe actual output
- List all seven validation checks in get_validation_errors() docblock
- Document the normalize/trim/regex pipeline in normalize_item_name()
- Document accepted aliases in validate_item_type()
- Extract private is_primary_index() helper to consolidate the repeated
  strtolower(trim($item->type)) === 'primary' check from get_item(),
  remove_item(), and get_validation_errors()
Centralizes the logic and allows it to be reused in several files consistently.
Comment thread src/Database/Traits/Base.php Outdated
@szepeviktor
Copy link
Copy Markdown
Contributor

PR sent #190

szepeviktor and others added 9 commits May 14, 2026 20:02
Rename `set_alias()` to `set_table_alias()`, make `get_table_name()`
protected, add `get_table_alias()`, and use the accessors when building
aliased SQL fragments.
…hreading

- Normalize first_letters() through the shared sanitizer and add tests for its behavior.
- Update sanitize_column_name() phpdoc to match the other sanitizer methods.

- Remove get_sql() positional argument plumbing across Query, Meta, and Parser so parsers use direct query access instead of threaded context arrays.
- This simplifies the call chain, reduces coupling, and keeps parser behavior tied to the active query object rather than passing context through multiple layers.
Probably future home of some Platform detection code.
JJJ and others added 5 commits May 26, 2026 15:20
copy_item() passed the full raw row to add_item() without stripping the UUID.
Column::validate_uuid() preserves any existing valid UUID unchanged ("UUIDs
should never change once they are set"), so every copy silently inherited the
original's UUID — two rows, one identifier.

Fix: unset 'uuid' from $save before the $data override merge. This lets
add_item() generate a fresh UUID via validate_uuid(). A UUID explicitly
provided in the $data override array is still respected (restored by the merge).

Adds test_copy_item_generates_distinct_uuid() to QueryCrudTest to pin the
corrected behaviour and catch any regression.
Introduce a small Log trait for structured in-memory diagnostic logging without adding another WordPress-specific dependency. The trait provides protected log collection, public log access/clearing helpers, and a no-op write_log() bridge for projects that want to forward entries to debug.log, error_log(), Monolog, Query Monitor, or another writer.

Compose Log into the shared Base trait so BerlinDB core objects can use it consistently, and add focused tests covering stored entries, level filtering, clearing, writer bridging, and empty-entry guards.

(Also update Trait doc-block text to be shorter)
write_log() is always called with a real entry — the array $entry = array()
default implied it could meaningfully be called with no args, which it cannot.
Remove the default from both the trait and the LogTestSubject override.

Add three tests that were absent from the initial pass:
- get_logs() returns [] before any entries are recorded
- entries are returned in insertion order
- clear_logs($level) re-indexes remaining entries so keys stay sequential
…, call sites

Adds a machine-readable $code field to every log entry so callers and
readers can match on stable event identifiers independently of message
text.

get_logs() and clear_logs() now accept an $args field/value filter and
a 'and'/'or' operator, backed by the new protected filter_logs() and
log_matches() helpers (both now carry explicit return type hints).

Adds four semantic log helpers that build consistent entries from
common diagnostic scenarios:
- log_empty_value()
- log_class_not_found()
- log_class_instantiation_failed()
- log_method_not_found()

Each helper accepts an optional $caller array (callable-style [object,
method]) and a $context override array whose special keys (code,
message, level) promote to the entry top level so callers can
customise without rebuilding the full entry. Supporting internals:
get_log_code(), get_log_caller_context(), get_log_class_short_name(),
normalize_log_key().

First real call sites land in Query::set_schema() and
Table::set_schema(), covering the four failure modes (empty schema,
class not found, instantiation failure, missing required method) with
stable event codes (query_schema_* / table_schema_*). Downstream
callers already guard with is_callable() so a partial schema object
does not crash; the log entry surfaces the misconfiguration for
debugging without aborting the boot sequence.

LogTest expanded to 10 assertions covering the new $code field, AND/OR
field filtering, clear_logs() re-indexing, write_log() bridge, and
empty-input guards. QuerySchemaLogTest and TableSchemaLogTest added to
verify each set_schema() failure path records the correct log code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a lightweight Log trait that gives every BerlinDB kernel
class (Query, Table, Column, Schema, Index, Row) a shared, structured
in-memory log. Entries carry a stable machine-readable code, level,
human-readable message, key-value context, timestamp, and source class.

Core API:
- log() — protected; normalises level/code, trims message, guards
  empties, calls the write_log() hook so subclasses can bridge to any
  external log destination without overriding the storage logic.
- get_logs( $args, $operator ) — returns all entries or those matching
  field/value pairs in AND (default) or OR mode.
- clear_logs( $args, $operator ) — removes matching entries (or all)
  and re-indexes the remaining array.
- write_log( $entry ) — no-op hook; override to forward to error_log(),
  Monolog, Query Monitor, etc.

filter_logs() / log_matches() carry explicit :array / :bool return
types and the full entry shape in their docblocks so PHPStan can track
the specific array shape through filtering without widening it.

First real call sites land in Query::set_schema() and
Table::set_schema(), each logging a single structured entry under the
stable code query_schema_unavailable / table_schema_unavailable when
the schema class is empty, missing, throws on construction, or lacks
the required method. Downstream callers already guard with is_callable()
so a partial schema object does not abort the boot sequence; the log
entry surfaces the misconfiguration for debugging.

Tests: LogTest (10), QuerySchemaLogTest (3), TableSchemaLogTest (3).

phpcs.xml updated from WordPress to WordPress-Core to align with
phpcs.xml.dist, and with the standard exclusion set from that baseline:
  - Generic.Files.OneObjectStructurePerFile excluded for tests/
    (multiple helper classes per file is the normal test pattern)
  - WordPress.DB.DirectDatabaseQuery / SlowDBQuery excluded for tests/
    (integration tests intentionally issue direct queries)
  - Squiz.Commenting.FunctionComment, DocComment.ShortNotCapital,
    DocComment.LongNotCapital, and related noise sniffs excluded globally
    (consistent with phpcs.xml.dist and eliminates ~150 pre-existing
    false positives in the test suite that were blocking CI)

PHPStan level 8: 0 errors. PHPCS: 0 errors. PHPUnit: 498/498.

Closes #153

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@JJJ JJJ linked an issue May 26, 2026 that may be closed by this pull request
JJJ and others added 17 commits May 26, 2026 18:34
Add coverage for PHP's same-family protected property behavior, where
a subclass can read a protected property from another object in the same
inheritance tree without invoking __get().

Also add Query-specific coverage showing the practical BerlinDB result:
external table_name access resolves through get_table_name(), while
sibling Query subclass access reads the raw configured/prefixed property
value directly.

Refs #46
Phase 1 — Schema object injection (Query + Table)

$table_schema on Query and $schema on Table now accept either a class
name string (the existing subclass pattern, unchanged) or a live Schema
instance. set_schema() checks instanceof first and short-circuits; the
existing string path is untouched. This makes both Boot-based
constructor injection and direct property assignment work:

  new Query( array( 'table_schema' => $schema ) )
  new Table( array( 'schema'       => $schema ) )

No existing subclass is affected — the instanceof guard only fires when
a Schema object is present.

Phase 2 — MySQL introspection factories

Column::from_mysql( array $row )
  Maps a single SHOW COLUMNS row (Field, Type, Null, Key, Default,
  Extra) to a fully constructed Column. The type string is parsed with
  a single regex into base_type, length, unsigned, and zerofill. Null,
  Key, and Default map to allow_null, primary, and default. Passing
  empty strings for pattern, cast, and validate triggers the existing
  auto-inference in sanitize_pattern(), sanitize_cast(), and
  sanitize_validation(), so the correct cast and format are inferred
  from the column type at construction time.

Schema::from_table( string $table )
  Queries SHOW COLUMNS FROM $table via the get_db() wrapper (no global
  $wpdb), maps each row through Column::from_mysql(), and returns a
  ready Schema. Returns an empty Schema if the table does not exist or
  the DB interface is unavailable.

Together these close the loop: a live MySQL table can now be reverse-
engineered into a working Query in a few lines:

  $schema = Schema::from_table( $wpdb->prefix . 'posts' );
  $query  = new Query( array( 'table_schema' => $schema ) );

PHPStan level 8: 0 errors. PHPCS: 0 errors. PHPUnit: 500/500.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new test classes covering the MySQL introspection factories introduced
in the previous commit. ColumnFromMysqlTest (33 tests) exercises from_mysql()
with hand-crafted SHOW COLUMNS rows and documents all sanitization side-effects
— type and extra are normalised to uppercase, string defaults collapse to '' due
to the validate-callback ordering constraint in sanitize_args(). SchemaFromTableTest
(27 integration tests) issues live SHOW COLUMNS queries against wp_posts and
wp_users and asserts column count, type, primary-key, date_query, and ordering
invariants that hold across all supported WordPress versions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Column::sanitize_default() was discarding string defaults because it called
validate($fallback) with only one argument, leaving the method's own $fallback
parameter at its empty-string default. Passing the value in both positions —
validate($value, $value) — preserves it when no validate callback is registered
at construction time (as is the case during from_mysql() introspection).

Environment::get_db_global() is a new static accessor that holds the DB
resolution logic; get_db() now delegates to it instead of duplicating it.
Schema::from_table() uses the static accessor directly (eliminating the
throwaway-instance smell) and wraps the SHOW COLUMNS query in
suppress_errors() so a missing table silently returns an empty Schema rather
than printing an HTML error block to the page.

Test suite: ColumnFromMysqlTest now builds shared column fixtures in
setUpBeforeClass() instead of repeating the same six-key array nine times;
the varchar-default assertion is corrected to 'publish' and the missing-key
assertion is corrected to false; SchemaFromTableTest drops the manual
suppress_errors() wrappers that are no longer needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Abstracts the database contract behind a Connection interface
(Interfaces/Connection.php) with a Wpdb adapter wrapping \wpdb and a
NullConnection adapter implementing the Null Object pattern. The
Environment trait now always returns a Connection — never false — so
the ~50 empty($db) early-return guards scattered across Query, Table,
Schema, Parsers, Operators, and Traits were all dead code and have been
removed. NullConnection::get_table_prefix() is a pass-through, which
preserves the pre-guard fallback behaviour in get_table_name() at zero
cost. PHPStan level 8 clean, PHPCS clean, 560/560 tests passing.
get_db_global() now caches the Wpdb adapter in a static array keyed by
global name. spl_object_id() guards the cache entry so a replaced $wpdb
instance (e.g. in tests) correctly yields a fresh adapter rather than
reusing the stale one. Because the adapter holds a reference to the
underlying \wpdb object rather than a snapshot, prefix mutations from
switch_to_blog() are visible through the cached adapter with no extra
work — the seven new EnvironmentTest cases cover this explicitly,
including a direct assertion that $wpdb->prefix mutations are reflected
after a cache hit.
Renames the protected accessor on the Environment trait from get_db()
to db() — shorter and consistent with non-get_ accessor conventions.
All 48 call sites are updated: the "// Get the database interface." /
"$db = $this->db();" two-line preamble is removed and uses are inlined
as $this->db()->method(). A deprecated get_db() alias is retained on
the trait so existing subclasses (EDD, Sugar Calendar, etc.) continue
to compile without changes. Two justified exceptions remain: Schema::from_table()
is static so it uses $db = self::get_db_global(), and Search::get_search_sql()
keeps a local $db because array_map requires an object reference for its
callable argument. Static adapter cache makes every inlined call a single
array lookup at no meaningful cost.

Fixes #69.
Closes #139. Adds 'cache_results' (default true) to query_var_defaults,
mirroring WP_Query/WP_Term_Query's pattern. When false, the result-list
cache is neither read nor written for that query, giving callers a
per-query escape hatch without resorting to wp_cache_flush(). The var is
excluded from cache key generation so cached and uncached calls for the
same args share the same key slot.
NullConnectionTest (PHPUnit\TestCase): verifies all Connection interface
methods return inert/safe values and that set_table_prefix/register_table
are no-ops. WpdbTest (WPIntegration\TestCase): verifies delegation to
the live $wpdb instance for prepare, esc_like, property accessors, and
the dynamic table-prefix registry, with state restoration after each
case. QueryGetResultsTest (PHPUnit\TestCase): uses a query subject that
captures args without a database to cover the three arg-mapping paths in
get_results(). ErrorTest (PHPUnit\TestCase): data-provider-driven coverage
of is_success() for all failure sentinels (false, null, 0), positive
values, and the WP_Error stash.
Use ALTER TABLE ... RENAME TO for Table::rename() so wpdb treats the
operation as DDL and returns a usable success value. Also prevent
Boot::parse_args() from restoring its internal args stash through
set_vars() when merging constructor defaults.

Add coverage for table copy, rename, repair, Boot argument stashing, and
existing Query hook smoke tests.
Add direct tests for Parser::get_cast_for_type() and sanitize_query()
behavior around invalid numeric children and OR relation tracking.

This complements the existing concrete parser integration coverage without
changing production code.
Add a CI workflow with separate PHPStan, PHPCS, and PHPUnit jobs for
pull requests and pushes to main, trunk, and release branches.

Allow the Docker PHPUnit runner to skip its built-in PHPCS step via
SKIP_PHPCS so CI job names map cleanly to the work they perform, while
preserving local test-runner behavior by default.
Add export-ignore rules so Composer dist archives only include the
runtime package files: license, readme, autoloader, composer metadata,
and src.

Declare PHP 8.1 as the supported minimum, update the Composer platform
to 8.1, and refresh the lock file metadata. PHP 8.0 is close at runtime,
but the current dev/test dependency stack requires PHP 8.1+.

Remove the stale hardcoded package version and add GitHub support links.

Expand PHPUnit CI to run on PHP 8.1 and 8.2, and give PHPStan a 1G
memory limit for more reliable CI runs.

Verified with composer validate, PHPCS, PHPStan, PHPUnit on PHP 8.1 and
PHP 8.2, and a Composer archive inspection.
Rewrite the README around installation, requirements, quick-start usage,
documentation links, and development workflow.

Add changelog, contribution guide, security policy, release checklist,
issue templates, pull request template, and markdownlint configuration.

Add Packagist-friendly Composer metadata and tighten export-ignore rules
so Composer archives stay focused on runtime package files.

Add a manual Pre-Release workflow that runs static checks, supported
PHPUnit lanes, changelog validation, and Composer archive inspection.
@JJJ JJJ marked this pull request as ready for review May 28, 2026 15:38
Copilot AI review requested due to automatic review settings May 28, 2026 15:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

@JJJ JJJ merged commit 5f50338 into release/2.1.0 May 28, 2026
8 checks passed
@JJJ JJJ deleted the release/3.0.0 branch May 28, 2026 15:39
@JJJ JJJ restored the release/3.0.0 branch May 28, 2026 15:42
@JJJ JJJ deleted the release/3.0.0 branch May 28, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notice developer about Schema class not exists

3 participants