Skip to content

Implement GH-13597: Add Pdo\Sqlite::ATTR_DQS - #25173

Open
Truta446 wants to merge 3 commits into
php:masterfrom
Truta446:sqlite-dqs-config
Open

Truta446 wants to merge 3 commits into
php:masterfrom
Truta446:sqlite-dqs-config

Conversation

@Truta446

@Truta446 Truta446 commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Fixes GH-13597

SQLite still accepts double-quoted string literals by default: if "abc" doesn't resolve to a column, it quietly becomes the string 'abc'. SQLite calls this a misfeature, and the sqlite3 shell has had it off since 3.41.0, but from PDO there was no way to turn it off. So a query with a typo'd column name, or one written for another database, can pass in PHP tests and then break elsewhere. That's the Doctrine case in the issue.

I added a boolean attribute that sets SQLITE_DBCONFIG_DQS_DML and SQLITE_DBCONFIG_DQS_DDL together. It works as a constructor option or via setAttribute(), and getAttribute() returns true while either one is still on. The default doesn't change. The constant only exists when building against SQLite 3.29.0+, since that's where the options were added.

$pdo = new Pdo\Sqlite('sqlite::memory:', options: [Pdo\Sqlite::ATTR_DQS => false]);
$pdo->query('SELECT "abc"'); // no such column: abc

I went with one switch instead of separate DML/DDL attributes because everyone asking for this wants both off. Happy to split it or rename it if you prefer. I left the SQLite3 class out to keep this small; it could get the same option in a follow-up.

@SakiTakamachi you offered to review this one on the issue, thanks.

Tested with the new gh13597.phpt plus ext/pdo_sqlite, ext/sqlite3 and ext/pdo on a master debug ASan build, with and without opcache/JIT.

SQLite accepts double-quoted string literals by default when a name in
double quotes does not resolve to a column, so SELECT "abc" silently
returns the string 'abc'. SQLite documents this as a misfeature, and the
sqlite3 shell turns it off since 3.41.0. Through PDO there was no way to
turn it off, so queries that only work because of this fallback pass in
PHP and then fail elsewhere.

Add a boolean connection attribute that sets SQLITE_DBCONFIG_DQS_DML and
SQLITE_DBCONFIG_DQS_DDL together. It can be passed to the constructor or
set later, and getAttribute() reports whether either is still enabled.
The default is unchanged. The constant only exists when building against
SQLite 3.29.0 or later, which is where these options were introduced.
@bohwaz

bohwaz commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Slightly off-topic but could there be also the same feature in the SQLite3 class?

@Truta446

Truta446 commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

Yes, I'd like to do that as a follow-up. SQLite3 has no attribute API like PDO, so it needs a small API of its own (a method or a constructor flag). I'll open a separate PR for it once this one settles, if that sounds good.

@bohwaz

bohwaz commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Yes, I'd like to do that as a follow-up. SQLite3 has no attribute API like PDO, so it needs a small API of its own (a method or a constructor flag). I'll open a separate PR for it once this one settles, if that sounds good.

That would be great, SQLite3 constructor has flags, so either set it in constructor flags, or add a SQLite3::enableDQS(bool $enable) method.

My issue with being able to set the attribute at any time is that its effect on active statements is unclear. If a statement has been created before enabling the attribute, does it change the existing statement or not? It's not always very clear with SQLite. I've had this issue with the authorizer, changing it after a statement has been prepared and executed once, will yield varying results for the following executions of the same statement, which is quite weird. But it reflects what SQLite offers, so it's "correct" but can be very complex to debug.

@Truta446

Truta446 commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

I checked how it behaves with already prepared statements. When sqlite3_db_config() actually changes one of these flags, SQLite expires all prepared statements on the connection, so they get recompiled with the new setting on their next execute. In practice a statement prepared with DQS on fails with "no such column" on its next execute after DQS is turned off, and works again once it is turned back on. Setting the same value again changes nothing. I added this case to the test so it's covered.

For SQLite3 I'll look at both options when I get to that PR. The constructor flags are passed to sqlite3_open_v2(), so DQS doesn't really fit there, and a method seems cleaner. Happy to follow whatever you prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Double quotes accepted unexpectedly for literal value escape in SQLite

2 participants