Skip to content

Conversation

@batopa
Copy link
Contributor

@batopa batopa commented Oct 14, 2025

This PR fixes an issue with default value of boolean type columns for SQLite database.

Building a table with TableSchema for example:

$s = new TableSchema('examples');
$s->addColumn('field1', [
    'type' => 'boolean',
    'null' => true,
    'default' => true,
]);

will produce

CREATE TABLE "examples" ("field1" BOOLEAN DEFAULT TRUE)

Once the table was created reading the schema we obtain an inconsistency columns for default value.

$schema = new SchemaCollection($connection);
$result = $schema->describe('examples');
debug($result->getColumn('field1'));

output

########## DEBUG ##########
[
  'type' => 'boolean',
  'length' => null,
  'null' => true,
  'default' => 'TRUE',
  'comment' => null,
  'precision' => null
]
###########################

The default is a string 'TRUE' but I expect a boolean true instead.

The same happens defining default as INT, for example 0. The results in this case will be 'default' => '0'.


Side note

To change code just in one place I modified the protected function _defaultValue signature. I think that it shouldn't have serious impact but let me know in case.

@dereuromark dereuromark added this to the 5.2.9 milestone Oct 14, 2025
author_id INT(11) NOT NULL,
unique_id UNSIGNED INTEGER NOT NULL,
published BOOLEAN DEFAULT 0,
reviewed BOOLEAN DEFAULT TRUE,
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this? Wouldnt it suffice to just map the other ones to numeric 0 as well?

Copy link
Member

Choose a reason for hiding this comment

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

Or maybe we just switch to using TRUE/FALSE then explicitly (in the future)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤔 I think that it is a more complete test case because \Cake\Database\Schema\SqliteSchemaDialect::columnSql():

  • passing 'default' => true produces BOOLEAN DEFAULT TRUE
  • passing 'default' => false produces BOOLEAN DEFAULT FALSE
  • passing 'default' => 1 produces BOOLEAN DEFAULT 1
  • passing 'default' => 0 produces BOOLEAN DEFAULT 0

so you can deal with both cases.

Anyway if you think it is unnecessary I can remove it.

Copy link
Member

Choose a reason for hiding this comment

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

Based on the other code changes above I think it should for now always produce only 0 and 1.

Copy link
Member

Choose a reason for hiding this comment

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

For reference, SQLite doesn't have a dedicated boolean type

SQLite does not have a separate Boolean storage class. Instead, Boolean values are stored as integers 0 (false) and 1 (true).

SQLite recognizes the keywords "TRUE" and "FALSE", as of version 3.23.0 (2018-04-02) but those keywords are really just alternative spellings for the integer literals 1 and 0 respectively.

https://sqlite.org/datatype3.html

Our other drivers use 0/1 in their schema reflection boolean logic, and it would be good to be consistent here.

@dereuromark
Copy link
Member

dereuromark commented Oct 14, 2025

I guess the one fail is probably an invalid test (data):

1) Cake\Test\TestCase\Database\Schema\SqliteSchemaDialectTest::testConvertColumn with data set #3 ('BOOLEAN', ['boolean', null])
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
Array &0 [
    'comment' => null,
-    'default' => 'Default value',
+    'default' => false,
    'length' => null,
    'null' => true,
    'type' => 'boolean',
]

Since it is "not empty" I wonder if this should map to true instead?

* @return string|int|bool|null
*/
protected function _defaultValue(string|int|null $default): string|int|null
protected function _defaultValue(string|int|null $default, ?string $type = null): string|int|bool|null
Copy link
Member

@dereuromark dereuromark Oct 14, 2025

Choose a reason for hiding this comment

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

How about as bugfix for 5.x:

  • You dont change the signature and map it to 0/1

Then in 5.next:

  • We can adjust the internal protected method to always return true/false for booleans from now on.

This is a bit more BC safe for people and consistent in the output then.

Copy link
Contributor Author

@batopa batopa Oct 14, 2025

Choose a reason for hiding this comment

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

Do you mean to leave the second parameter ?string $type = null and change the return value for type boolean always to int?

Copy link
Member

@dereuromark dereuromark Oct 14, 2025

Choose a reason for hiding this comment

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

Minimal changeset that makes the code work, yes, for a bugfix release.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need to worry too much about BC here. I doubt that there are userland extensions to the sqlite schema dialect.

Copy link
Member

@dereuromark dereuromark Oct 15, 2025

Choose a reason for hiding this comment

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

I still think it would be cleaner if we had one single way of interpretation.

Our other drivers use 0/1 in their schema reflection boolean logic, and it would be good to be consistent here.

So its settled then.

@markstory markstory self-assigned this Oct 14, 2025
Co-authored-by: Mark Scherer <[email protected]>
@batopa
Copy link
Contributor Author

batopa commented Oct 14, 2025

I guess the one fail is probably an invalid test (data):

1) Cake\Test\TestCase\Database\Schema\SqliteSchemaDialectTest::testConvertColumn with data set #3 ('BOOLEAN', ['boolean', null])
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
Array &0 [
    'comment' => null,
-    'default' => 'Default value',
+    'default' => false,
    'length' => null,
    'null' => true,
    'type' => 'boolean',
]

Since it is "not empty" I wonder if this should map to true instead?

Uhm... I'm not sure how to handle this situation.
The issue is that filter_var($default, FILTER_VALIDATE_BOOLEAN) return false trying to validate 'Default value'.
We could add the flag FILTER_NULL_ON_FAILURE to trace that the default is not valid and take a decision on top of that.

But what is the action to take in this scenario? SQLite allows to set any default i.e. CREATE table examples (field BOOLEAN DEFAULT not_a_boolean); won't throw any error.

Any suggestion?

@dereuromark
Copy link
Member

But what is the action to take in this scenario? SQLite allows to set any default i.e. CREATE table examples (field BOOLEAN DEFAULT not_a_boolean); won't throw any error.

What does it convert it to? 0 or 1? whatever the real result, thats your answer IMO.

- Fixed failing tests
- Aligned on int cast for boolean column default values.
- Made validation stricter by not handling arbitrary string-integers
@markstory
Copy link
Member

I pushed up a commit with my suggestions and fixed failing tests.

@ADmad ADmad added the needs squashing The pull request should be squashed before merging label Oct 15, 2025
@dereuromark dereuromark merged commit dd8e460 into cakephp:5.x Oct 15, 2025
15 checks passed
@batopa batopa deleted the fix/sqlite-boolean-default branch October 15, 2025 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

defect needs squashing The pull request should be squashed before merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants