Skip to content
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

All notable changes to this project will be documented in this file. This project adhere to the [Semantic Versioning](http://semver.org/) standard.

## [3.1.4] 2025-10-16

* Fix - Amend the `get_*` query methods to properly handle array values.
Comment thread
dpanta94 marked this conversation as resolved.
Outdated

## [3.1.3] 2025-10-08

* Fix - Fix the `get_current_schema` method to cache the schema of each implementation.
* Fix - Amend the `get_current_schema` method to cache the schema of each implementation.

[3.1.3]: https://github.com/stellarwp/schema/releases/tag/3.1.3

## [3.1.2] 2025-10-02

* Fix - Fix the `update_many` method to properly check if the transaction was successful.
* Fix - Amend the `update_many` method to properly check if the transaction was successful.

[3.1.2]: https://github.com/stellarwp/schema/releases/tag/3.1.2

Expand Down
41 changes: 33 additions & 8 deletions src/Schema/Traits/Custom_Table_Query_Methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ public static function get_total_items( array $args = [] ): int {
* Updates multiple rows into the table.
*
* @since 3.0.0
* @since 3.1.4 Enabled unfolding the value if is an array.
*
* @param array<mixed> $entries The entries to update.
*
Expand Down Expand Up @@ -372,7 +373,7 @@ public static function update_many( array $entries ): bool {

[ $value, $placeholder ] = self::prepare_value_for_query( $column, $value );

$set_statement[] = $database::prepare( "%i = {$placeholder}", ...array_filter( [ $column, $value ], static fn( $v ) => null !== $v ) );
$set_statement[] = $database::prepare( "%i = {$placeholder}", ...array_filter( [ $column, ...self::ensure_array( $value ) ], static fn( $v ) => null !== $v ) );
}

$set_statement = implode( ', ', $set_statement );
Expand Down Expand Up @@ -672,6 +673,7 @@ protected static function get_join_parts( string $join_table, string $join_condi
*
* @since 3.0.0
* @since 3.1.1 Added the $order_by parameter.
* @since 3.1.4 Enabled unfolding the value if is an array.
*
* @param string $column The column to get the models by.
* @param mixed $value The value to get the models by.
Expand All @@ -690,12 +692,12 @@ public static function get_all_by( string $column, $value, string $operator = '=

$database = Config::get_db();
$results = [];
foreach ( static::fetch_all_where( $database::prepare( "WHERE %i {$operator} {$placeholder}", ...array_filter( [ $column, $value ], static fn( $v ) => null !== $v ) ), $limit, ARRAY_A, $order_by ) as $task_array ) {
if ( empty( $task_array[ static::uid_column() ] ) ) {
foreach ( static::fetch_all_where( $database::prepare( "WHERE %i {$operator} {$placeholder}", ...array_filter( [ $column, ...self::ensure_array( $value ) ], static fn( $v ) => null !== $v ) ), $limit, ARRAY_A, $order_by ) as $data_array ) {
if ( empty( $data_array[ static::uid_column() ] ) ) {
continue;
}

$results[] = static::transform_from_array( self::amend_value_types( $task_array ) );
$results[] = static::transform_from_array( self::amend_value_types( $data_array ) );
}

return $results;
Expand All @@ -705,25 +707,35 @@ public static function get_all_by( string $column, $value, string $operator = '=
* Gets the first model by a column.
*
* @since 3.0.0
* @since 3.1.4 Enabled unfolding the value if is an array.
* @since 3.1.4 Added the $operator parameter.
*
* @param string $column The column to get the model by.
* @param mixed $value The value to get the model by.
* @param string $operator The operator to use.
*
* @return ?mixed The model, or `null` if no model is found.
*
* @throws InvalidArgumentException If the column does not exist.
* @throws InvalidArgumentException If the operator is invalid.
*/
public static function get_first_by( string $column, $value ) {
public static function get_first_by( string $column, $value, string $operator = '=' ) {
[ $value, $placeholder ] = self::prepare_value_for_query( $column, $value );

$operator = strtoupper( $operator );

if ( ! in_array( $operator, self::operators(), true ) ) {
throw new InvalidArgumentException( "Invalid operator: {$operator}." );
}

$database = Config::get_db();
$task_array = static::fetch_first_where( $database::prepare( "WHERE %i = {$placeholder}", ...array_filter( [ $column, $value ], static fn( $v ) => null !== $v ) ), ARRAY_A );
$data_array = static::fetch_first_where( $database::prepare( "WHERE %i {$operator} {$placeholder}", ...array_filter( [ $column, ...self::ensure_array( $value ) ], static fn( $v ) => null !== $v ) ), ARRAY_A );
Comment thread
dpanta94 marked this conversation as resolved.

if ( empty( $task_array[ static::uid_column() ] ) ) {
if ( empty( $data_array[ static::uid_column() ] ) ) {
return null;
}

return static::transform_from_array( self::amend_value_types( $task_array ) );
return static::transform_from_array( self::amend_value_types( $data_array ) );
}

/**
Expand Down Expand Up @@ -918,4 +930,17 @@ public static function cast_value_based_on_type( string $type, $value ) {
throw new InvalidArgumentException( "Unsupported column type: {$type}." );
}
}

/**
* Ensures the value is an array.
*
* @since 3.1.4
*
* @param mixed $value The value to ensure is an array.
*
* @return array<mixed> The value as an array.
*/
private static function ensure_array( $value ): array {
return is_array( $value ) ? $value : [ $value ];
Comment thread
lucatume marked this conversation as resolved.
}
}
236 changes: 236 additions & 0 deletions tests/wpunit/Traits/Custom_Table_Query_MethodsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
<?php

namespace StellarWP\Schema\Tests\Traits;

use StellarWP\Schema\Register;
use StellarWP\Schema\Tests\SchemaTestCase;
use StellarWP\Schema\Tests\Traits\Table_Fixtures;
use StellarWP\Schema\Columns\Integer_Column;
use StellarWP\Schema\Columns\String_Column;
use StellarWP\Schema\Columns\ID;
use StellarWP\Schema\Columns\Column_Types;
use StellarWP\Schema\Collections\Column_Collection;
use StellarWP\Schema\Tables\Contracts\Table;
use StellarWP\Schema\Tables\Table_Schema;
use StellarWP\DB\DB;

class Custom_Table_Query_MethodsTest extends SchemaTestCase {
use Table_Fixtures;

/**
* @before
* @after
*/
public function drop_tables() {
$this->get_query_test_table()->drop();
}

/**
* @test
*/
public function should_update_multiple_with_array_values() {
$table = $this->get_query_test_table();
Register::table( $table );

// Insert test data
$table::insert( [
'name' => 'Test 1',
'slug' => 'test-1',
'status' => 1,
] );

$id1 = DB::last_insert_id();

$table::insert( [
'name' => 'Test 2',
'slug' => 'test-2',
'status' => 1,
] );

$id2 = DB::last_insert_id();

// Update multiple rows using array values
$updated = $table::update_many( [
[
'id' => $id1,
'name' => 'Updated Test 1',
],
[
'id' => $id2,
'name' => 'Updated Test 2',
],
] );

$this->assertEquals( 2, $updated );

// Verify the updates
$result1 = $table::get_first_by( 'slug', 'test-1' );
$this->assertEquals( 'Updated Test 1', $result1['name'] );

$result2 = $table::get_first_by( 'slug', 'test-2' );
$this->assertEquals( 'Updated Test 2', $result2['name'] );
}

/**
* @test
*/
public function should_get_all_by_with_array_values() {
$table = $this->get_query_test_table();
Register::table( $table );

// Insert test data
$table::insert( [
'name' => 'Test 1',
'slug' => 'test-1',
'status' => 1,
] );

$id1 = DB::last_insert_id();

$table::insert( [
'name' => 'Test 2',
'slug' => 'test-2',
'status' => 1,
] );

$id2 = DB::last_insert_id();

$table::insert( [
'name' => 'Test 3',
'slug' => 'test-3',
'status' => 0,
] );

$id3 = DB::last_insert_id();

// Get all by status using array (simulating IN operator scenario)
$results = $table::get_all_by( 'status', [ 1, 0 ], 'IN' );

$this->assertCount( 3, $results );

$this->assertEquals( 'Test 1', $results[0]['name'] );
$this->assertEquals( 'Test 2', $results[1]['name'] );
$this->assertEquals( 'Test 3', $results[2]['name'] );

$this->assertEquals( 1, $results[0]['status'] );
$this->assertEquals( 1, $results[1]['status'] );
$this->assertEquals( 0, $results[2]['status'] );
}

/**
* @test
*/
public function should_get_first_by_with_array_values() {
$table = $this->get_query_test_table();
Register::table( $table );

// Insert test data
$table::insert( [
'name' => 'First Match',
'slug' => 'first-match',
'status' => 1,
] );

$table::insert( [
'name' => 'Second Match',
'slug' => 'second-match',
'status' => 1,
] );

// Get first by slug
$result = $table::get_first_by( 'slug', [ 'second-match' ], 'NOT IN' );

$this->assertNotNull( $result );
$this->assertEquals( 'First Match', $result['name'] );
}

/**
* @test
*/
public function should_update_multiple_with_integer_array_values() {
$table = $this->get_query_test_table();
Register::table( $table );

// Insert test data
$table::insert( [
'name' => 'Active Item',
'slug' => 'active-item',
'status' => 1,
] );

$id1 = DB::last_insert_id();

// Update using integer value
$updated = $table::update_many( [
[
'id' => $id1,
'status' => 0,
],
] );

$this->assertEquals( 1, $updated );

// Verify the update
$result = $table::get_first_by( 'slug', 'active-item' );
$this->assertEquals( 0, $result['status'] );
}

/**
* @test
*/
public function should_handle_scalar_values_in_queries() {
$table = $this->get_query_test_table();
Register::table( $table );

// Insert test data with scalar values
$table::insert( [
'name' => 'Scalar Test',
'slug' => 'scalar-test',
'status' => 1,
] );

$id = DB::last_insert_id();

$this->assertIsInt( $id );
$this->assertGreaterThan( 0, $id );

// Verify scalar value retrieval
$result = $table::get_first_by( 'id', $id );
$this->assertEquals( 'Scalar Test', $result['name'] );
}

/**
* Get a test table for query method testing.
*/
private function get_query_test_table() {
return new class extends Table {
const SCHEMA_VERSION = '1.0.0';

protected static $base_table_name = 'query_test';
protected static $group = 'test';
protected static $schema_slug = 'test-query';

public static function get_schema_history(): array {
$table_name = static::table_name( true );
$callable = function() use ( $table_name ) {
$columns = new Column_Collection();

$columns[] = ( new ID( 'id' ) )->set_length( 11 )->set_type( Column_Types::INT );
$columns[] = ( new String_Column( 'name' ) )->set_length( 255 );
$columns[] = ( new String_Column( 'slug' ) )->set_length( 255 )->set_is_index( true );
$columns[] = ( new Integer_Column( 'status' ) )->set_length( 1 )->set_default( 0 );

return new Table_Schema( $table_name, $columns );
};

return [
static::SCHEMA_VERSION => $callable,
];
}

public static function transform_from_array( array $result_array ) {
return $result_array;
}
};
}
}