Skip to content

SQLite: do not ask for the metadata of a column that has no table - #38976

Merged
AndriySvyryd merged 1 commit into
dotnet:mainfrom
HuzaifaChaudary:fix/sqlite-getstream-expression-column
Sep 14, 2026
Merged

AndriySvyryd merged 1 commit into
dotnet:mainfrom
HuzaifaChaudary:fix/sqlite-getstream-expression-column

Conversation

@HuzaifaChaudary

Copy link
Copy Markdown
Contributor

Fixes #27776

the issue asks whether the column metadata calls in GetStream can be handed a null , and they can .

what happens

GetStream looks for a rowid column so it can hand back a SqliteBlob . for a value that came from an expression there is no underlying column , so sqlite3_column_database_name , sqlite3_column_table_name and sqlite3_column_origin_name all answer null . i printed them for SELECT 42, substr("Content", 1, 3) FROM "Case" :

ordinal=0 name=42                         db=<null> table=<null> origin=<null>
ordinal=1 name=substr("Content", 1, 3)    db=<null> table=<null> origin=<null>

the search starts from a null database and table name , so the literal in column 0 matches it , and its null origin name goes into sqlite3_table_column_metadata . sqlite answers SQLITE_MISUSE and GetStream throws :

Microsoft.Data.Sqlite.SqliteException : SQLite Error 21: 'bad parameter or other API misuse'
   at Microsoft.Data.Sqlite.SqliteDataRecord.GetStream(Int32 ordinal) in SqliteDataRecord.cs:341

the same expression on its own is fine , because there is no second column for the search to reach . that is why the report says removing the 42 makes it work . it is also why a real column sitting next to a literal is fine , a non null table name does not match the literal's null one .

this is the ado.net repro of ericsink/SQLitePCL.raw#479 , which asked whether the bug was in the wrapper or in the caller . it is the caller .

the fix

a column with no database or table name has no row to open a blob on , so return the MemoryStream , which is what already happens when no rowid column is found :

if (blobDatabaseName == null
    || blobTableName == null)
{
    return new MemoryStream(GetCachedBlob(ordinal), false);
}

GetTextReader and GetFieldValue<Stream> both go through GetStream , so they are fixed with it .

tests

three , next to the existing GetStream ones .

  • GetStream_works_when_expression_and_a_literal_in_the_same_query , the report's shape . throws SQLITE_MISUSE on main , gives a MemoryStream with the right three bytes after
  • GetTextReader_works_when_expression_and_a_literal_in_the_same_query , same path through the text reader . same before and after
  • GetStream_Blob_works_when_a_literal_is_in_the_same_query , a real blob column with a literal beside it . passes on main and after , so the guard is not swallowing the blob case

on main that is 2 failures out of 714 , and both are the two above . with the change Microsoft.Data.Sqlite.sqlite3.Tests is 707 pass , sqlite3mc 708 pass , EFCore.Sqlite.Tests 896 pass , all 0 failed . EFCore.Sqlite.FunctionalTests is 38042 pass , and its 177 failures are all mod_spatialite.dylib not being installed on this mac , the same 177 as before the change .


🤖 Generated with Claude Code

https://claude.ai/code/session_01VfnFMKDUZWENKk7Vpe6GS7

GetStream looked for a rowid column to open a blob on, and for a value
that came from an expression the database, table and origin names are all
null. Those nulls were passed to sqlite3_table_column_metadata, which
answers SQLITE_MISUSE, so reading a stream from an expression threw as
soon as any other expression column was in the same query.

- A column with no database or table name now returns the MemoryStream
  fallback, the same as when no rowid column is found
- GetTextReader and GetFieldValue<Stream> go through GetStream, so they
  are fixed with it

Fixes dotnet#27776
Copilot AI lite review requested due to automatic review settings September 12, 2026 18:38
@HuzaifaChaudary
HuzaifaChaudary requested a review from a team as a code owner September 12, 2026 18:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused guard addresses the failure while tests cover both fallback and SqliteBlob behavior.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@AndriySvyryd AndriySvyryd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for your contribution!

@AndriySvyryd
AndriySvyryd merged commit b23b056 into dotnet:main Sep 14, 2026
16 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Sep 15, 2026
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.

Possible misuse of sqlite3_column_table_name et al

3 participants