diff --git a/Directory.Packages.props b/Directory.Packages.props
index c9cedac6..6736eefa 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -2,7 +2,7 @@
truetrue
- 2.1.1
+ 2.1.2
diff --git a/README.md b/README.md
index af2b84b8..2143081a 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,16 @@ Use it when you need:
measured 2026-10-11)**, the **RC.3 sweep of 2,897 tests across 16 suites**, and **backward
compatibility**
-> **Current release: v2.1.0 (2026-10-10) — the .NET 11 / C# 15 line, on `master`.** **Published on
+> **Current release: v2.1.2 (2026-10-26) — the .NET 11 / C# 15 line, on `master`.** A data-correctness
+> patch with no API changes: a **bound string parameter holding an apostrophe, a doubled quote or a
+> backslash was re-scanned as SQL**, so the value could land in the neighbouring column, and a value
+> ending in a backslash could not be written at all. One scanner now owns SQL's and SQLite's escaping
+> rules for every literal reader on the statement path. Validated by **core 2,107 tests / 0 failed,
+> VectorSearch 255 / 0, EF Core 116 / 0, linq2DB 24 / 0**, and a two-file runnable reproduction ships in
+> [`docs/sharpcoredb-known-issues.md`](docs/sharpcoredb-known-issues.md) §7. Full notes:
+> [`docs/CHANGELOG.md`](docs/CHANGELOG.md) › `[2.1.2]`.
+
+> **Previous release — v2.1.0 (2026-10-10) — the .NET 11 / C# 15 line, on `master`.** **Published on
> NuGet.org** (all 27 packable packages) from `master`, `net11.0` only. Highlights: the fixed-width inline capacity
> ships at 24 bytes by default on both storage paths (worth **+34 %** on the batched multi-row INSERT
> shape and **0,63× → 0,87×** on the tracked fair-PK INSERT ratio), the overflow arena appends a row's payloads in one call instead of one
@@ -73,17 +82,17 @@ Use it when you need:
## Requirements and the two release lines
-| | **v2.1.0 — this line (`master`)** | **v2.0.0.3 — parked line (`release/v2.0`)** |
+| | **v2.1.2 — this line (`master`)** | **v2.0.0.3 — parked line (`release/v2.0`)** |
|---|---|---|
| Target framework | `net11.0` only (every project under `src`) | `net10.0` |
| Language | C# 15 (`LangVersion=preview`, always on) | C# 14 |
| SDK | .NET 11 RC, pinned in `global.json` (`11.0.100-rc.1.26425.128`, `allowPrerelease`, `rollForward: latestPatch`) | .NET 10 (`10.0.x`) |
| Pipeline | `.github/workflows/ci.yml` | `.github/workflows/ci-net10.yml` |
-| Packages | `2.1.0` — the 27 packable projects under `src` | `2.0.0.3` |
+| Packages | `2.1.2` — the 27 packable projects under `src` | `2.0.0.3` |
| Status | current release, receives new work | servicing only |
- **Pick one line per solution.** The package references are line-specific: a `net10.0` project uses the
- `2.0.0.x` packages, a `net11.0` project the `2.1.0` ones. Do not mix them in one solution.
+ `2.0.0.x` packages, a `net11.0` project the `2.1.2` ones. Do not mix them in one solution.
- **The .NET 11 SDK is required to build** — the `global.json` pin is deliberate and `dotnet test` is not
this repository's runner (Microsoft.Testing.Platform is; run the built test executable, or see
[`docs/INDEX.md`](docs/INDEX.md)).
@@ -115,7 +124,47 @@ Use it when you need:
---
-## What's new in v2.1.0 (current release)
+## What's new in v2.1.2 (current release)
+
+**A data-correctness patch with no API changes.** `ExecuteSQL(sql, parameters)` does not bind values at the
+engine level: it renders each one back **into the statement text** and hands that text to the ordinary
+parser, so the formatter and the statement scanners have to agree about escaping — and they did not. A
+bound `TEXT` value holding an apostrophe, a doubled quote or a backslash was re-scanned as SQL: the value
+could land in the **neighbouring column**, and a value ending in a backslash could not be written at all
+(`Mismatched parentheses in VALUES clause`). Any consumer storing prose, source code or Windows paths
+through a parameter was exposed, and both faces of the failure were silent.
+
+The rules now live once, in [`SqlLiteralScanner`](src/SharpCoreDB/Services/SqlLiteralScanner.cs), and every
+literal reader on the statement path uses it: the `VALUES` and `SET` splitters, the matching-parenthesis
+scan, all four parameter extractors, the batch `INSERT` parsers, `FastSqlLexer.ReadString`, the expression
+tokenizer, the `WHERE` tokenizer (`EvaluateWhere` split on spaces and truncated *any* quoted value at its
+first space) and the server's `$n` → `?` translation. A doubled quote is one quote and does not close the
+literal; a backslash is data. `ParseValue` removes exactly one quote pair and collapses the doubling (a
+parameter holding `'` used to store an empty string), a quoted `''` stores an empty string as SQLite does
+instead of `NULL`, `UPDATE … SET col = @v` splits at the first `=` instead of every `=`, and a tuple that
+cannot fit its statement throws before any row is written — naming the table, the tuple index, both counts
+and the column list — instead of shifting values right.
+
+```csharp
+// Before 2.1.2 the body landed in `Source` (or the INSERT threw "Mismatched parentheses").
+// After: both round-trip byte-for-byte, Windows paths and prose included.
+db.ExecuteSQL("INSERT INTO Passages (Id, Source, Body) VALUES (@id, @src, @body)",
+ new Dictionary
+ {
+ ["@id"] = 7,
+ ["@src"] = @"C:\src\app\",
+ ["@body"] = @"It's O(n log n) — see docs\draft.pdf, 'quoted' too"
+ });
+```
+
+Validated on this tree: **core 2,107 tests / 0 failed**, VectorSearch **255 / 0**, EF Core **116 / 0**,
+linq2DB **24 / 0**, and `SharpCoreDB.CI.slnf` builds clean. A two-file runnable reproduction with the
+before/after console output ships in [`docs/sharpcoredb-known-issues.md`](docs/sharpcoredb-known-issues.md)
+§7, and the consumer that reported it — SharpAgent's code-evidence index, which writes source text and
+Windows paths as `TEXT` — runs its **144 indexing tests green** against this build with **no code change**.
+Full list: [`docs/CHANGELOG.md`](docs/CHANGELOG.md) › `[2.1.2]`.
+
+## What's new in v2.1.0 (previous release)
The v1.x benchmark gap (point reads/updates/deletes **16–52x behind SQLite** because every update
appended a new row) is closed as an engine behaviour. The numbers below are re-measured on
@@ -235,7 +284,7 @@ Headline changes, grouped by area. Everything below is in the shipped `2.1.0` pa
### 1) Embedded mode
```bash
-dotnet add package SharpCoreDB --version 2.1.0
+dotnet add package SharpCoreDB --version 2.1.2
```
```csharp
@@ -272,8 +321,8 @@ gRPC endpoint: `https://localhost:5001`
Install client/server packages:
```bash
-dotnet add package SharpCoreDB.Server.Core --version 2.1.0
-dotnet add package SharpCoreDB.Client --version 2.1.0
+dotnet add package SharpCoreDB.Server.Core --version 2.1.2
+dotnet add package SharpCoreDB.Client --version 2.1.2
```
> The server host is a standalone executable (`src/SharpCoreDB.Server`), distributed as the
@@ -286,6 +335,10 @@ dotnet add package SharpCoreDB.Client --version 2.1.0
## Previous releases
+- **v2.1.1** — patch, no API changes: a bound `float[]` reaches a `VECTOR(n)` column as a vector,
+ `ORDER BY vec_distance_*() LIMIT k` reaches the vector index instead of scanning, `information_schema`
+ is answered by the engine itself (so REST and the embedded API see the catalog the PG handshake does),
+ and the 18 % coverage floor is measured and enforced in CI.
- **v2.0.0.3** — the net10.0 / C# 14 stable line, parked on `release/v2.0` and built by `ci-net10.yml`
(the complete package list is above). Engine foundation it carries: fixed-width record layout by default
for new PK tables, in-place UPDATE/DELETE with commit-time tombstones, and the reopen data-integrity
@@ -300,50 +353,50 @@ dotnet add package SharpCoreDB.Client --version 2.1.0
## Available NuGet packages
-### v2.1.0 — current release (`master`, net11.0 / C# 15 preview)
+### v2.1.2 — current release (`master`, net11.0 / C# 15 preview)
```bash
# Core
-dotnet add package SharpCoreDB --version 2.1.0
+dotnet add package SharpCoreDB --version 2.1.2
# Server/client
-dotnet add package SharpCoreDB.Server.Core --version 2.1.0
-dotnet add package SharpCoreDB.Server.Protocol --version 2.1.0
-dotnet add package SharpCoreDB.Client --version 2.1.0
-dotnet add package SharpCoreDB.Client.Protocol --version 2.1.0
-dotnet add package SharpCoreDB.Data.Provider --version 2.1.0
+dotnet add package SharpCoreDB.Server.Core --version 2.1.2
+dotnet add package SharpCoreDB.Server.Protocol --version 2.1.2
+dotnet add package SharpCoreDB.Client --version 2.1.2
+dotnet add package SharpCoreDB.Client.Protocol --version 2.1.2
+dotnet add package SharpCoreDB.Data.Provider --version 2.1.2
# Engines and extensions
-dotnet add package SharpCoreDB.Analytics --version 2.1.0
-dotnet add package SharpCoreDB.VectorSearch --version 2.1.0
-dotnet add package SharpCoreDB.Search --version 2.1.0
-dotnet add package SharpCoreDB.HybridSearch --version 2.1.0
-dotnet add package SharpCoreDB.Graph --version 2.1.0
-dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.0
-dotnet add package SharpCoreDB.Distributed --version 2.1.0
-dotnet add package SharpCoreDB.Provider.Sync --version 2.1.0
-dotnet add package SharpCoreDB.Provider.YesSql --version 2.1.0
-dotnet add package SharpCoreDB.EntityFrameworkCore --version 2.1.0
-dotnet add package SharpCoreDB.Extensions --version 2.1.0
-dotnet add package SharpCoreDB.Identity --version 2.1.0
-dotnet add package SharpCoreDB.Serilog.Sinks --version 2.1.0
+dotnet add package SharpCoreDB.Analytics --version 2.1.2
+dotnet add package SharpCoreDB.VectorSearch --version 2.1.2
+dotnet add package SharpCoreDB.Search --version 2.1.2
+dotnet add package SharpCoreDB.HybridSearch --version 2.1.2
+dotnet add package SharpCoreDB.Graph --version 2.1.2
+dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.2
+dotnet add package SharpCoreDB.Distributed --version 2.1.2
+dotnet add package SharpCoreDB.Provider.Sync --version 2.1.2
+dotnet add package SharpCoreDB.Provider.YesSql --version 2.1.2
+dotnet add package SharpCoreDB.EntityFrameworkCore --version 2.1.2
+dotnet add package SharpCoreDB.Extensions --version 2.1.2
+dotnet add package SharpCoreDB.Identity --version 2.1.2
+dotnet add package SharpCoreDB.Serilog.Sinks --version 2.1.2
# Optional architecture packages
-dotnet add package SharpCoreDB.EventSourcing --version 2.1.0
-dotnet add package SharpCoreDB.Projections --version 2.1.0
-dotnet add package SharpCoreDB.CQRS --version 2.1.0
+dotnet add package SharpCoreDB.EventSourcing --version 2.1.2
+dotnet add package SharpCoreDB.Projections --version 2.1.2
+dotnet add package SharpCoreDB.CQRS --version 2.1.2
# Optional functional adapters
-dotnet add package SharpCoreDB.Functional --version 2.1.0
-dotnet add package SharpCoreDB.Functional.Dapper --version 2.1.0
-dotnet add package SharpCoreDB.Functional.EntityFrameworkCore --version 2.1.0
-dotnet add package SharpCoreDB.Functional.Linq2DB --version 2.1.0
+dotnet add package SharpCoreDB.Functional --version 2.1.2
+dotnet add package SharpCoreDB.Functional.Dapper --version 2.1.2
+dotnet add package SharpCoreDB.Functional.EntityFrameworkCore --version 2.1.2
+dotnet add package SharpCoreDB.Functional.Linq2DB --version 2.1.2
# Hosting and tooling
-dotnet add package SharpCoreDB.Aspire.Hosting --version 2.1.0
+dotnet add package SharpCoreDB.Aspire.Hosting --version 2.1.2
```
-> The 27 commands above are the complete packable set of this line. **`2.1.0` is a stable release**, so
+> The 27 commands above are the complete packable set of this line. **`2.1.2` is a stable release**, so
> NuGet selects it by default for a `net11.0` project; a `net10.0` project resolves to the `2.0.0.3` line
> instead. Pin the version explicitly (as above) when you want to be certain, and tick *Include
> prerelease* only if you are deliberately testing an RC build.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index e849ddcd..266f14fa 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -7,9 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
-> **Where this bucket ends.** `2.1.1` was cut from this line's `HEAD` on 2026-10-25, so every entry this
-> bucket had collected since the `v2.1.0` tag — the list below — **shipped in `[2.1.1]`**. New entries
-> start here.
+> **Where this bucket ends.** `2.1.2` was cut from this line's `HEAD` on 2026-10-26, so the entry this
+> bucket had collected since the `v2.1.1` tag — the statement-path literal scanner below — **shipped in
+> `[2.1.2]`**. New entries start here.
+
+## [2.1.2] - 2026-10-26
+
+> **What this release is.** A patch release with no API changes, cut from `v2.1.1..HEAD`, and a data-correctness
+> one rather than a performance one. `ExecuteSQL(sql, parameters)` never binds: it renders every value back
+> into the statement text and hands that text to the ordinary parser, so the formatter and the scanners have
+> to agree about escaping — and they did not. A bound `TEXT` value holding an apostrophe, a doubled quote or a
+> backslash was re-scanned as SQL, and the damage was silent: the value landed in the neighbouring column, or
+> the statement was rejected with "Mismatched parentheses in VALUES clause". Any consumer that stores prose,
+> source code or Windows paths through a parameter was exposed; SharpAgent's code-evidence index hit both
+> faces of it. One scanner now owns the rules (`Services/SqlLiteralScanner.cs`), every literal reader on the
+> statement path uses it, and a tuple that still cannot fit its statement fails loudly instead of shifting
+> values. Verified on this tree: core **2107 / 0 failed**, VectorSearch **255 / 0**, EF Core **116 / 0**,
+> linq2DB **24 / 0**, `SharpCoreDB.CI.slnf` builds clean, and the reproduction published in
+> `docs/sharpcoredb-known-issues.md` §7 prints the fixed output when extracted from the document.
+
+### Fixed
+
+- **A bound string parameter was re-parsed as SQL text, so an apostrophe or a backslash in the value corrupted the row — it could land in the next column, or the statement failed with "Mismatched parentheses in VALUES clause".** `ExecuteSQL(sql, parameters)` renders every value back into the statement text (`ParameterBinder.FormatParameter` emits `'it''s'` for `it's`, SQL's doubled-quote convention) and hands the *rendered* statement to the ordinary parser, so the formatter and the scanner have to agree about escaping — and they did not: the VALUES scanner dropped the quote of a doubled pair (standard SQL doubling was not an escape) and let a backslash suppress a quote toggle, which also meant a value ending in a backslash could not be written at all. SharpAgent's code-evidence index hit it as rows rejected with "Mismatched parentheses in VALUES clause" and, worse, as a value silently landing in the neighbouring column (`Invalid VECTOR literal '… iterations)'` on `embedding`). The rules are now SQL's and SQLite's, defined once in `Services/SqlLiteralScanner.cs` and used by every literal reader on the statement path — the VALUES and `SET` splitters, the matching-parenthesis scan, all four parameter extractors, the batch INSERT parsers, `FastSqlLexer.ReadString`, the expression tokenizer, the `WHERE` tokenizer (`Table.Scanning.EvaluateWhere`, which split on spaces and therefore truncated *any* quoted value at its first space) and the wire protocol's `$n` → `?` translation (which needed `InternalsVisibleTo("SharpCoreDB.Server.Core")`): a doubled quote is one quote and does not close the literal, and a backslash is data. `ParseValue` now removes exactly **one** pair of quotes and collapses the doubling — the old `while`-strip removed every edge quote, so a parameter holding `'` stored an empty string, and `'it''s'` reached storage as `it''s`. `UPDATE … SET col = @v` splits at the first `=` instead of every `=` (a value containing `=` silently dropped that assignment), a quoted empty literal stores an empty string as SQLite does (a trailing `''` used to become `NULL`), and a tuple that still produces more values than the statement can consume throws **before any row is written**, naming the table, the tuple index, both counts and the column list, instead of shifting values into the next column. Verified red→green: `ParameterLiteralRoundTripTests` (74 cases — apostrophes, doubled quotes, backslash before a quote, trailing backslash, commas, brackets, braces, newlines, non-ASCII, `NULL`, the empty string, `'`, `''`, `\`, each through an interior and a trailing INSERT value, multi-row INSERT, positional `?`, `UPDATE … SET`, and a `WHERE val = @val` lookup) failed 32 of 57 assertions before the fix and passes 74/74 after; `InsertValuesParsingTests` was corrected to SQL semantics (`'it''s'` → `it's`, a trailing backslash is data, a trailing `''` is an empty string) and the suites are **core 2107 / 0 failed**, VectorSearch 255 / 0, EF Core 116 / 0, linq2DB 24 / 0 (`SharpCoreDB.CI.slnf` builds clean). Two latent test bugs surfaced on the way: `EncryptionCoverageTests` and `WritePathProfilerTests` interpolated `{i * 0.5}` straight into SQL, which renders `0,5` on a comma-decimal machine and is a four-value tuple — the old parser silently dropped the overflow value, the new arity check rejects it, so both now format invariantly. `docs/sharpcoredb-known-issues.md` §7 now carries a self-contained, runnable reproduction (two files, `dotnet run`) with the before/after console output, and the same cases are pinned by `ParameterLiteralRoundTripTests`.
## [2.1.1] - 2026-10-25
diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md
index 3628477b..1dbf5ce7 100644
--- a/docs/PROJECT_STATUS.md
+++ b/docs/PROJECT_STATUS.md
@@ -1,16 +1,26 @@
# SharpCoreDB Project Status
-**Version:** 2.1.0 (`master`, net11.0 / C# 15 preview) · 2.0.0.3 (net10.0 / C# 14 stable, parked on `release/v2.0`)
-**Status:** ✅ v2.1.0 promoted to the main branch · ✅ v2.0 stable line parked on `release/v2.0` · ✅ Performance-first line
-**Last Updated:** October 10, 2026
+**Version:** 2.1.2 (`master`, net11.0 / C# 15 preview) · 2.0.0.3 (net10.0 / C# 14 stable, parked on `release/v2.0`)
+**Status:** ✅ v2.1.2 released on the main branch · ✅ v2.0 stable line parked on `release/v2.0` · ✅ Performance-first line
+**Last Updated:** October 26, 2026
## Current Status
+The **current release is `2.1.2` (2026-10-26)** — a data-correctness patch with no API changes: a bound
+`TEXT` parameter holding an apostrophe, a doubled quote or a backslash was re-scanned as SQL by the
+statement path, so the value could land in the neighbouring column, or the statement was rejected with
+"Mismatched parentheses in VALUES clause". One scanner (`Services/SqlLiteralScanner.cs`) now owns SQL's
+and SQLite's escaping rules for every literal reader on the statement path. Validated on the `2.1.2` tree:
+core **2,107 tests / 0 failed**, VectorSearch **255 / 0**, EF Core **116 / 0**, linq2DB **24 / 0**. Notes:
+[`CHANGELOG.md`](CHANGELOG.md) › `[2.1.2]`; the runnable reproduction is in
+[`sharpcoredb-known-issues.md`](sharpcoredb-known-issues.md) §7.
+
The **v2.1 line (`2.1.0`)** is validated and packaged, and was promoted to `master` on **2026-10-10**
(see [`net11/PROMOTION_TO_MAIN.md`](net11/PROMOTION_TO_MAIN.md)): **2,897 tests across 16
suites — 0 failed, 0 errors, 16 skipped** (core 1,916), the write-path regression gate **passed**, and
the full benchmark arm set (comparative, fair-PK, pure-default, PageBased, multi-row INSERT) measured
in the product-default regime. Release notes: [`2.1.0-RC.3_WHAT_CHANGED.md`](2.1.0-RC.3_WHAT_CHANGED.md).
+`2.1.1` followed on **2026-10-25** with the SQL vector fixes; `2.1.2` is the current patch.
The **v2.0 stable packages (`2.0.0.3`)** are the net10.0 / C# 14 line, parked on `release/v2.0`, including:
@@ -34,9 +44,9 @@ The **v2.0 stable packages (`2.0.0.3`)** are the net10.0 / C# 14 line, parked on
- ✅ **The RC.3 line was published to NuGet.org (2026-09-17)** — all 27 packages at `2.1.0-RC.3` from
`release/v2.1.0.0-RC.3` (`Manual NuGet Publish` run 35244818191); `SharpCoreDB.Search`,
`SharpCoreDB.HybridSearch` and `SharpCoreDB.Aspire.Hosting` ship on this line for the first time
-- ⏳ **2.1.0 is prepared on `master` and not yet tagged or pushed** — the owner's flip (`git tag -a v2.1.0`
- + `git push`) is what publishes the 27 packages at `2.1.0`; version metadata, CI and the shipped
- package documentation are already aligned for it (see [`net11/PROMOTION_TO_MAIN.md`](net11/PROMOTION_TO_MAIN.md))
+- ✅ **2.1.0 was published from `master`, and `2.1.1` followed as a patch (2026-10-25)** — the publish
+ trigger is the `push: branches: [master]` job in `.github/workflows/ci.yml`, which packs and pushes the
+ 27 packages; `2.1.2` is the current release. History: [`CHANGELOG.md`](CHANGELOG.md).
- ✅ **Linux/macOS data-integrity defect fixed** — the AES-GCM cipher caches were shared across threads,
which is only safe on Windows (dotnet/runtime#53320); they are thread-affine now, so concurrent
encryption cannot corrupt a cipher's native state or repeat a nonce off Windows
diff --git a/docs/Vectors/README.md b/docs/Vectors/README.md
index a8ff5b02..ab17e28c 100644
--- a/docs/Vectors/README.md
+++ b/docs/Vectors/README.md
@@ -76,10 +76,10 @@ This is the foundation for:
```bash
# Core database
-dotnet add package SharpCoreDB --version 2.1.0
+dotnet add package SharpCoreDB --version 2.1.2
# Vector search extension
-dotnet add package SharpCoreDB.VectorSearch --version 2.1.0
+dotnet add package SharpCoreDB.VectorSearch --version 2.1.2
```
### 2. Register Vector Search
diff --git a/docs/analytics/README.md b/docs/analytics/README.md
index fff63daa..90de6a47 100644
--- a/docs/analytics/README.md
+++ b/docs/analytics/README.md
@@ -32,7 +32,7 @@ The SharpCoreDB Analytics Engine provides high-performance data aggregation, win
### Installation
```bash
-dotnet add package SharpCoreDB.Analytics --version 2.1.1
+dotnet add package SharpCoreDB.Analytics --version 2.1.2
```
### Basic Aggregation
diff --git a/docs/cqrs/README.md b/docs/cqrs/README.md
index c1bdf88f..ca4352b2 100644
--- a/docs/cqrs/README.md
+++ b/docs/cqrs/README.md
@@ -54,7 +54,7 @@ Non-goals:
## 2. Installation
```bash
-dotnet add package SharpCoreDB.CQRS --version 2.1.1
+dotnet add package SharpCoreDB.CQRS --version 2.1.2
```
---
diff --git a/docs/graphrag/README.md b/docs/graphrag/README.md
index 3ececa68..54ff269a 100644
--- a/docs/graphrag/README.md
+++ b/docs/graphrag/README.md
@@ -49,9 +49,9 @@ For package-level mapping, see `../FEATURE_MATRIX.md`.
## Quick start
```bash
-dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.1
-dotnet add package SharpCoreDB.Graph --version 2.1.1
-dotnet add package SharpCoreDB.VectorSearch --version 2.1.1
+dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.2
+dotnet add package SharpCoreDB.Graph --version 2.1.2
+dotnet add package SharpCoreDB.VectorSearch --version 2.1.2
```
```csharp
diff --git a/docs/manual/quickstart.md b/docs/manual/quickstart.md
index 8ea34ff8..103ece29 100644
--- a/docs/manual/quickstart.md
+++ b/docs/manual/quickstart.md
@@ -8,7 +8,7 @@
## 1.1 Install
```bash
-dotnet add package SharpCoreDB --version 2.1.1
+dotnet add package SharpCoreDB --version 2.1.2
```
The engine targets **.NET 11** and **C# 15**. No native dependencies, no external processes —
diff --git a/docs/scdb/PRODUCTION_GUIDE.md b/docs/scdb/PRODUCTION_GUIDE.md
index fd4008f7..04ce3840 100644
--- a/docs/scdb/PRODUCTION_GUIDE.md
+++ b/docs/scdb/PRODUCTION_GUIDE.md
@@ -46,7 +46,7 @@ This guide covers deploying SharpCoreDB with the **SCDB (SharpCore DataBase)** s
dotnet --version # Should show 11.0 or higher
# NuGet Package
-dotnet add package SharpCoreDB --version 2.1.1
+dotnet add package SharpCoreDB --version 2.1.2
```
The 2.0.x servicing line on `release/v2.0` targets .NET 10 instead; match the runtime to the package
diff --git a/docs/sharpcoredb-known-issues.md b/docs/sharpcoredb-known-issues.md
index ce6e3cdd..16ea7fd4 100644
--- a/docs/sharpcoredb-known-issues.md
+++ b/docs/sharpcoredb-known-issues.md
@@ -192,6 +192,235 @@ error message.
---
+## 7. ✅ FIXED — A bound string parameter is re-parsed as SQL text, so quotes and backslashes corrupt the row
+
+> ✅ **FIXED (2026-10-26, `master` @ 9461c74e + this change).** Found while building SharpAgent's code-evidence
+> index, which writes source text, comments and Windows paths as `TEXT` values. Every reproduction below is now
+> a test: `tests/SharpCoreDB.Tests/ParameterLiteralRoundTripTests.cs` (74 cases) failed 32 of 57 before the fix
+> and the core suite is **2107 / 0 failed** after it.
+
+### Runnable reproduction
+
+Two files, no test framework, ~40 lines — the whole defect and the whole fix are visible in the console output.
+Put them in `artifacts/repro/` (two levels below the repo root, so the `..\..\src` project reference is right) and
+run `dotnet run -c Release`.
+
+`Repro.csproj`
+
+```xml
+
+
+
+ Exe
+ net11.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+```
+
+`Program.cs`
+
+```csharp
+using Microsoft.Extensions.DependencyInjection;
+using SharpCoreDB;
+
+var services = new ServiceCollection();
+services.AddSharpCoreDB();
+var factory = services.BuildServiceProvider().GetRequiredService();
+
+var dir = Path.Combine(Path.GetTempPath(), "scdb-issue7-" + Guid.NewGuid().ToString("N"));
+Directory.CreateDirectory(dir);
+var db = factory.Create(dir, "pw", isReadOnly: false, config: new DatabaseConfig { NoEncryptMode = true });
+db.ExecuteSQL("CREATE TABLE t (id INTEGER PRIMARY KEY, head TEXT, val TEXT, tail TEXT)");
+
+// @val sits in the middle of the tuple, so a value that is split at its own comma moves into `tail`.
+string[] middle =
+[
+ "it's fine, really",
+ @"a stray \' apostrophe, then a comma",
+ @"ends with a backslash \",
+ "plain text",
+];
+
+for (int i = 0; i < middle.Length; i++)
+{
+ Try(i + 1, "INSERT INTO t VALUES (@id, 'head', @val, 'tail')", middle[i]);
+}
+
+// The empty string as the *last* value: the tuple ends in ''.
+Try(99, "INSERT INTO t VALUES (@id, 'head', 'tail', @val)", string.Empty);
+
+void Try(int id, string sql, string value)
+{
+ Console.WriteLine($"@val = [{value}]");
+ try
+ {
+ db.ExecuteSQL(sql, new Dictionary { ["id"] = id, ["val"] = value });
+ var row = db.ExecuteQuery($"SELECT * FROM t WHERE id = {id}")[0];
+ Console.WriteLine($" val={Show(row["val"])} tail={Show(row["tail"])}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($" {ex.GetType().Name}: {ex.Message}");
+ }
+}
+
+static string Show(object? value) => value is null or DBNull ? "" : $"[{value}]";
+```
+
+**Before the fix** — `HEAD` `9461c74e`, the tree this report was filed against (output copied from that run; the
+`←` notes are annotations, and `Show` prints `` for a null so an empty string and a null cannot be confused):
+
+```text
+@val = [it's fine, really]
+ val=[its fine] tail=[really, tail] ← apostrophe gone, literal split at its comma
+@val = [a stray \' apostrophe, then a comma]
+ InvalidOperationException: Mismatched parentheses in VALUES clause
+@val = [ends with a backslash \]
+ InvalidOperationException: Mismatched parentheses in VALUES clause
+@val = [plain text]
+ val=[plain text] tail=[tail]
+@val = []
+ val=[tail] tail= ← '' dropped: val overwritten, tail set to NULL
+```
+
+**After the fix** — same program, same machine, no other change:
+
+```text
+@val = [it's fine, really]
+ val=[it's fine, really] tail=[tail]
+@val = [a stray \' apostrophe, then a comma]
+ val=[a stray \' apostrophe, then a comma] tail=[tail]
+@val = [ends with a backslash \]
+ val=[ends with a backslash \] tail=[tail]
+@val = [plain text]
+ val=[plain text] tail=[tail]
+@val = []
+ val=[tail] tail=[]
+```
+
+Every line of that program is also a test case in `tests/SharpCoreDB.Tests/ParameterLiteralRoundTripTests.cs`
+(74 cases), so the regression cannot come back silently.
+
+**Fix (what changed).** The formatter and the scanners now share **one** escaping convention, defined once in
+`src/SharpCoreDB/Services/SqlLiteralScanner.cs` and used by every literal reader on the statement path:
+
+- **The rule is SQL's, and therefore SQLite's:** inside `'…'` a doubled quote is one quote of data and does not
+ close the literal, and a backslash is data. A backslash can never be an escape here — it is a legal last
+ character of a value (a Windows path), and a scanner that lets it suppress the closing quote cannot represent
+ that value in a statement at all.
+- **Converted call sites:** `SqlParser.ParseInsertValues` (now purely structural — it returns each element as
+ written and `ParseValue` reads the literal), `SqlParser.FindMatchingCloseParen`,
+ `SqlInPredicate.SplitTopLevelComma`, all four `PreparedStatement` extractors, `Database.Batch`
+ (`ParseValues`, `ParseValuesToArray`, `ParseValueFast`), `SingleFileDatabase.Batch` (`ParseValueList`,
+ `TrimQuotes`), `FastSqlLexer.ReadString`, the `TypeConverter` tokenizer, the `WHERE` tokenizer
+ (`Table.Scanning.EvaluateWhere` — splitting on spaces truncated *any* quoted value at its first space),
+ `Table.QueryHelpers`, `Table.Collation`, `SqlInPredicate`'s IN/tuple value parsing, `SingleFileTable`,
+ `WhereClauseAnalyzer`, `SqlParser.PerformanceOptimizations`, `SqlParserExtensions`, `DatabaseExtensions`, and
+ the wire protocol's `$n` → `?` translation (`BinaryProtocolHandler`, which needed
+ `InternalsVisibleTo("SharpCoreDB.Server.Core")`).
+- **`ParseValue` removes exactly one pair of quotes** and collapses the doubling. The old `while`-strip removed
+ every edge quote, so a parameter holding `'` stored an empty string; and it left `''` in place, so `'it''s'`
+ reached storage as `it''s`.
+- **A quoted empty literal is a value.** `(1, '')` stores an empty string, as SQLite does; only a literally
+ absent element is dropped. A *trailing* `''` used to become `NULL`, which made an empty string impossible to
+ round-trip.
+- **`UPDATE … SET col = @v` splits at the first `=`**, not at every `=`: a value containing `=` (a query string,
+ a source line) silently dropped the whole assignment before.
+- **Safety net (recommendation 1):** a VALUES tuple that produces more values than the statement can consume now
+ throws **before any row is written**, naming the table, the tuple index, both counts and the column list,
+ instead of shifting values into the neighbouring column.
+- **Two latent test bugs surfaced:** `EncryptionCoverageTests` and `WritePathProfilerTests` interpolated
+ `{i * 0.5}` straight into SQL, which renders `0,5` on a comma-decimal machine and is a **four**-value tuple.
+ The old parser silently dropped the overflow; the new arity check rejects it (SQLite rejects it too), so those
+ two tests now format invariantly.
+
+**Not changed, deliberately:** values are still rendered into the statement text for `ExecuteSQL(sql, params)`.
+Recommendation 3 (build the row from the parameter objects and skip the text round trip) remains an
+optimisation, not a correctness need now that the convention is shared and pinned.
+
+**Location:** `src/SharpCoreDB/Services/PreparedStatements.cs` (`ParameterBinder.FormatParameter`, lines 478-510),
+`src/SharpCoreDB/Services/SqlParser.Core.cs:193` (`Execute(sql, parameters)` → `BindParameters`), and
+`src/SharpCoreDB/Services/SqlParser.DML.cs` (`ParseMultiRowInsertValues` 584-628, `FindMatchingCloseParen`
+633-661, `ParseInsertValues` 664-823).
+
+- `ExecuteSQL(sql, parameters)` renders every value back **into the statement text**
+ (`FormatParameter` returns `'it''s'` for `it's`, `[0.5, 0.25]` for a `float[]`, `NULL` for null) and then
+ hands the *rendered* statement to the ordinary parser. The value therefore becomes parser input, and
+ correctness depends on the formatter and the literal scanner agreeing exactly.
+- They do not agree. The scanner toggles quote state on every `'` whose previously **appended** character is
+ not a backslash (`SqlParser.DML.cs:709-717`): standard SQL quote-doubling is not an escape, and there is no
+ `toggles == 3` branch, so after a doubled quote the scanner considers itself **outside** the literal for the
+ rest of it.
+
+**Reproduced** with `CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT)` and
+`INSERT INTO t VALUES (1, @text, 'tail')`:
+
+| `@text` | What is stored |
+|---|---|
+| `it's fine, really` | `a = "its fine"`, `b = "really, tail"` — apostrophe dropped, literal split at the comma, **tail shifted into the next column** |
+| `a stray \' apostrophe, then a comma` | `InvalidOperationException: Mismatched parentheses in VALUES clause` |
+| `ends with a backslash \` | `InvalidOperationException: Mismatched parentheses in VALUES clause` |
+| `the mask (see below, the reader) wins, and then it records iterations)` | correct — parens and commas alone are safe, which is why the defect hides |
+
+**Consequence:** a value may land in the **neighbouring column**, including a non-text one. SharpAgent saw it as
+`Invalid VECTOR literal '… iterations)'` on `embedding`, and before that as rows rejected with "Mismatched
+parentheses". Silent data corruption is the more dangerous half: nothing is thrown for the apostrophe case.
+
+**Why the scanners disagree.** There are four independent quote scanners, each with slightly different rules:
+
+- `SqlParser.ParseInsertValues` / `FindMatchingCloseParen` — backslash-aware, doubling-unaware.
+- `SqlInPredicate.SplitTopLevelComma` (`SqlInPredicate.cs:408-456`, the one UPDATE's `SET` list uses) —
+ quote/paren/bracket-aware, doubling-unaware, backslash-unaware (`inString = c != quote`).
+- `PreparedStatement.ExtractParameterPositions` / `ExtractNamedParameters` (`PreparedStatements.cs:101-194`) —
+ backslash-aware, doubling-unaware.
+- `ParameterExtractor` — its own copy of the same idea.
+
+And `InsertValuesParsingTests` currently **pins the corruption as intended**:
+`DoubledQuote_IsDroppedAndContentConcatenated_NotEscaped` asserts `'it''s'` → `its`, and
+`BackslashEscapedQuote_IsKeptVerbatim_AndDoesNotToggle` asserts that a backslash suppresses the toggle.
+
+**Workaround in SharpAgent:** never send free-form text through `ExecuteSQL(sql, parameters)`. Writes go through
+`ITable.Insert` / `UpdateByPrimaryKey` with native values (`src/SharpCoreAgent.Indexing/IndexSql.cs`), which
+never renders a statement, so no escaping question arises. This is a consumer-side workaround, not a fix: the
+same corruption is reachable from any `ExecuteSQL` call with a text parameter.
+
+**Recommendation (in this order):** ✅ 1, 2 and 4 are implemented (see *Fix* above). 3 remains an optional
+optimisation. The original analysis is kept below as the record of what was wrong.
+
+1. **Assert the arity.** After `ParseMultiRowInsertValues`, compare `rowValues.Count` with the resolved column
+ list and throw naming the column, the literal index and the offset instead of letting a shifted value reach
+ `ParseValue`. Converts silent corruption into a loud, diagnosable error, with no hot-path cost.
+2. **One scanner, one escaping convention.** Extract a single `SqlLiteralScanner` used by the VALUES splitter,
+ `FindMatchingCloseParen`, `SplitTopLevelComma` and both `Extract*` methods, implementing SQL's actual rules
+ (`''` inside a literal is one quote and does **not** close it; a backslash is data, not an escape), and make
+ `FormatParameter` emit exactly what that scanner reads back — `'` → `''`, and either escape `\` as `\\` or,
+ preferably, stop treating `\` as an escape anywhere. Then "any string round-trips byte-exact" becomes a
+ testable property rather than a coincidence.
+3. **Stop rendering values into statements.** For simple single-table `INSERT … VALUES (…, @p, …)` /
+ `UPDATE … SET col = @p WHERE pk = @pk`, build the row dictionary from the parameter objects and call the
+ table API the SQL path already ends in (`Table.Insert`, `Table.InsertBatch`, `Table.UpdateByPrimaryKey`).
+ No text is produced, so nothing can be mis-escaped, and the statement avoids a format-then-parse round trip.
+ The engine already shape-gates a "simple select" plan, so the precedent exists.
+4. **Replace the two pinning tests** with round-trip tests (`ExecuteSQL` with a parameter, read back, compare
+ byte-exact) over: apostrophes, doubled quotes, backslashes before quotes, trailing backslashes, braces,
+ parens, brackets, embedded commas, newlines and non-ASCII. Keep `InsertValuesParsingTests`' literal
+ `INSERT … VALUES` cases that assert genuine SQL behaviour, and correct `'it''s'` → `it's`.
+
+**Guardrail for step 2:** the multi-row VALUES scan is a measured hot path (comments at
+`SqlParser.DML.cs:572-583` and `:436-450` record the numbers). Any rewrite must keep the slice fast path for
+literals without doubling or backslashes, and re-run `--multirowinsert` plus the `WritePathProfiler`
+attribution before merge.
+
+---
+
## Priority summary
| # | Issue | Severity | Status in ServiceMq.SharpCoreDb | In SharpCoreDB 2.1.0 |
@@ -201,4 +430,5 @@ error message.
| 3 | Single-file no-op index | 🟠 Medium | Directory mode forced | **Fixed** — `SingleFileTable` keeps a PK hash index |
| 4 | No read-after-write flush | 🟡 Low | Flush calls in provider | **Fixed** — `ExecuteQuery` flushes pending batch writes |
| 5 | SQL validator `@` prefix | 🟡 Low | Keys without `@` prefix | **Fixed** — `NormalizeKey` strips `@` / `:` |
-| 6 | INTEGER → Int32 | 🟡 Low | `BIGINT` columns | **Still the default** — opt in with `UseSqliteIntegerAffinity` |
\ No newline at end of file
+| 6 | INTEGER → Int32 | 🟡 Low | `BIGINT` columns | **Still the default** — opt in with `UseSqliteIntegerAffinity` |
+| 7 | Bound text re-parsed as SQL | 🔴 High | Write through `ITable.Insert` instead of SQL text | **Fixed** — one `SqlLiteralScanner` (SQL/SQLite: a doubled quote is one quote, a backslash is data) behind every scanner; `ParseValue` strips one pair and un-collapses doubling; a tuple with too many values is rejected. `ParameterLiteralRoundTripTests` 74/74 |
\ No newline at end of file
diff --git a/src/SharpCoreDB.Analytics/NuGet.README.md b/src/SharpCoreDB.Analytics/NuGet.README.md
index e63428aa..b113d2e2 100644
--- a/src/SharpCoreDB.Analytics/NuGet.README.md
+++ b/src/SharpCoreDB.Analytics/NuGet.README.md
@@ -53,7 +53,7 @@ Unlock enterprise-grade analytics with 100+ aggregate functions, window function
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Analytics --version 2.1.1
+dotnet add package SharpCoreDB.Analytics --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+
diff --git a/src/SharpCoreDB.Analytics/SharpCoreDB.Analytics.csproj b/src/SharpCoreDB.Analytics/SharpCoreDB.Analytics.csproj
index e85c6750..011495ba 100644
--- a/src/SharpCoreDB.Analytics/SharpCoreDB.Analytics.csproj
+++ b/src/SharpCoreDB.Analytics/SharpCoreDB.Analytics.csproj
@@ -4,7 +4,7 @@
net11.0enableenable
- 2.1.1
+ 2.1.2SharpCoreDB.Analytics
@@ -13,7 +13,7 @@
SharpCoreDB.AnalyticsAnalytics extensions for SharpCoreDB: aggregates, window functions, SIMD-accelerated analytics, and time-series helpers for .NET 11.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdanalytics;aggregates;window-functions;statistics;timeseries;database;sharpcoredb;net11;csharp15MIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj
index cd41e596..46e67c02 100644
--- a/src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj
+++ b/src/SharpCoreDB.Aspire.Hosting/SharpCoreDB.Aspire.Hosting.csproj
@@ -16,7 +16,7 @@
SharpCoreDB.Aspire.Hosting
- 2.1.1
+ 2.1.2.NET Aspire hosting integration for the SharpCoreDB network server
@@ -33,7 +33,7 @@
SharpCoreDB.jpgtruesnupkg
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): .NET Aspire hosting package for SharpCoreDB.Server - container image ghcr.io/mpcoredeveloper/sharpcoredb-server with HTTPS gRPC (5001) and HTTPS REST API (8443) endpoints. Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/server/ASPIRE_INTEGRATION.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): .NET Aspire hosting package for SharpCoreDB.Server - container image ghcr.io/mpcoredeveloper/sharpcoredb-server with HTTPS gRPC (5001) and HTTPS REST API (8443) endpoints. Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/server/ASPIRE_INTEGRATION.md
diff --git a/src/SharpCoreDB.CQRS/NuGet.README.md b/src/SharpCoreDB.CQRS/NuGet.README.md
index 9d1f4cbb..acf1156d 100644
--- a/src/SharpCoreDB.CQRS/NuGet.README.md
+++ b/src/SharpCoreDB.CQRS/NuGet.README.md
@@ -28,7 +28,7 @@ CQRS and outbox primitives for `SharpCoreDB`.
## Installation
```bash
-dotnet add package SharpCoreDB.CQRS --version 2.1.1
+dotnet add package SharpCoreDB.CQRS --version 2.1.2
```
## Documentation
diff --git a/src/SharpCoreDB.CQRS/SharpCoreDB.CQRS.csproj b/src/SharpCoreDB.CQRS/SharpCoreDB.CQRS.csproj
index 92b1ae3d..ebd7f80a 100644
--- a/src/SharpCoreDB.CQRS/SharpCoreDB.CQRS.csproj
+++ b/src/SharpCoreDB.CQRS/SharpCoreDB.CQRS.csproj
@@ -11,7 +11,7 @@
$(NoWarn);1591;SA1101;SA1633;SA1309SharpCoreDB.CQRS
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.CQRS
@@ -24,7 +24,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.Client.Protocol/SharpCoreDB.Client.Protocol.csproj b/src/SharpCoreDB.Client.Protocol/SharpCoreDB.Client.Protocol.csproj
index 2b483600..866e62f0 100644
--- a/src/SharpCoreDB.Client.Protocol/SharpCoreDB.Client.Protocol.csproj
+++ b/src/SharpCoreDB.Client.Protocol/SharpCoreDB.Client.Protocol.csproj
@@ -9,7 +9,7 @@
SharpCoreDB.Client.Protocol
- 2.1.1
+ 2.1.2Client-side protocol implementation for SharpCoreDB (gRPC, binary)
@@ -26,7 +26,7 @@
SharpCoreDB.jpgtruesnupkg
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.Client/NuGet.README.md b/src/SharpCoreDB.Client/NuGet.README.md
index 8be2f102..b5ab6c42 100644
--- a/src/SharpCoreDB.Client/NuGet.README.md
+++ b/src/SharpCoreDB.Client/NuGet.README.md
@@ -27,7 +27,7 @@
## Installation
```bash
-dotnet add package SharpCoreDB.Client --version 2.1.1
+dotnet add package SharpCoreDB.Client --version 2.1.2
```
## Documentation
diff --git a/src/SharpCoreDB.Client/SharpCoreDB.Client.csproj b/src/SharpCoreDB.Client/SharpCoreDB.Client.csproj
index 15cc0662..0c2dce07 100644
--- a/src/SharpCoreDB.Client/SharpCoreDB.Client.csproj
+++ b/src/SharpCoreDB.Client/SharpCoreDB.Client.csproj
@@ -9,7 +9,7 @@
SharpCoreDB.Client
- 2.1.1
+ 2.1.2.NET client library for SharpCoreDB network server (ADO.NET-like API)
diff --git a/src/SharpCoreDB.Data.Provider/NuGet.README.md b/src/SharpCoreDB.Data.Provider/NuGet.README.md
index 76ce3ae4..98d8d328 100644
--- a/src/SharpCoreDB.Data.Provider/NuGet.README.md
+++ b/src/SharpCoreDB.Data.Provider/NuGet.README.md
@@ -57,7 +57,7 @@ while (reader.Read())
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Data.Provider --version 2.1.1
+dotnet add package SharpCoreDB.Data.Provider --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+
diff --git a/src/SharpCoreDB.Data.Provider/SharpCoreDB.Data.Provider.csproj b/src/SharpCoreDB.Data.Provider/SharpCoreDB.Data.Provider.csproj
index 75998472..3e435a4f 100644
--- a/src/SharpCoreDB.Data.Provider/SharpCoreDB.Data.Provider.csproj
+++ b/src/SharpCoreDB.Data.Provider/SharpCoreDB.Data.Provider.csproj
@@ -16,7 +16,7 @@
https://github.com/MPCoreDeveloper/SharpCoreDBhttps://github.com/MPCoreDeveloper/SharpCoreDBado.net;database;data-provider;sharpcoredb
- 2.1.1
+ 2.1.2gitNuGet.README.mdSharpCoreDB.jpg
@@ -24,7 +24,7 @@
snupkgSharpCoreDB.Data.ProviderData provider layer for SharpCoreDB on .NET 11.
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdfalse
diff --git a/src/SharpCoreDB.Distributed/NuGet.README.md b/src/SharpCoreDB.Distributed/NuGet.README.md
index 001fe3eb..2fc942a0 100644
--- a/src/SharpCoreDB.Distributed/NuGet.README.md
+++ b/src/SharpCoreDB.Distributed/NuGet.README.md
@@ -167,7 +167,7 @@ Multi-master replication, distributed transactions, and automatic sharding - Pha
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Distributed --version 2.1.1
+dotnet add package SharpCoreDB.Distributed --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+
diff --git a/src/SharpCoreDB.Distributed/SharpCoreDB.Distributed.csproj b/src/SharpCoreDB.Distributed/SharpCoreDB.Distributed.csproj
index d3e3d3d0..340ef6ec 100644
--- a/src/SharpCoreDB.Distributed/SharpCoreDB.Distributed.csproj
+++ b/src/SharpCoreDB.Distributed/SharpCoreDB.Distributed.csproj
@@ -15,7 +15,7 @@
SharpCoreDB.Distributed
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Distributed
@@ -28,7 +28,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.EntityFrameworkCore/NuGet.README.md b/src/SharpCoreDB.EntityFrameworkCore/NuGet.README.md
index 3fb2117c..a380f776 100644
--- a/src/SharpCoreDB.EntityFrameworkCore/NuGet.README.md
+++ b/src/SharpCoreDB.EntityFrameworkCore/NuGet.README.md
@@ -67,7 +67,7 @@ var users = await context.Users.Where(u => u.IsActive).ToListAsync();
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.EntityFrameworkCore --version 2.1.1
+dotnet add package SharpCoreDB.EntityFrameworkCore --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+, EntityFrameworkCore v8.0+
diff --git a/src/SharpCoreDB.EntityFrameworkCore/SharpCoreDB.EntityFrameworkCore.csproj b/src/SharpCoreDB.EntityFrameworkCore/SharpCoreDB.EntityFrameworkCore.csproj
index 58c82450..921fa38d 100644
--- a/src/SharpCoreDB.EntityFrameworkCore/SharpCoreDB.EntityFrameworkCore.csproj
+++ b/src/SharpCoreDB.EntityFrameworkCore/SharpCoreDB.EntityFrameworkCore.csproj
@@ -8,13 +8,13 @@
SharpCoreDB.EntityFrameworkCore
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.EntityFrameworkCore
- Entity Framework Core provider for SharpCoreDB encrypted database engine. Built for .NET 11 with C# 15. Supports Windows, Linux, macOS, Android, iOS, and IoT/embedded devices with platform-specific optimizations. Compatible with SharpCoreDB 2.1.1.
+ Entity Framework Core provider for SharpCoreDB encrypted database engine. Built for .NET 11 with C# 15. Supports Windows, Linux, macOS, Android, iOS, and IoT/embedded devices with platform-specific optimizations. Compatible with SharpCoreDB 2.1.2.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdsharpcoredb;entityframeworkcore;efcore;database;encryption;net11;csharp15;android;ios;mobile;iot;arm64;x64MIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.EventSourcing/NuGet.README.md b/src/SharpCoreDB.EventSourcing/NuGet.README.md
index 60a869d2..c70e08d7 100644
--- a/src/SharpCoreDB.EventSourcing/NuGet.README.md
+++ b/src/SharpCoreDB.EventSourcing/NuGet.README.md
@@ -28,7 +28,7 @@ Event store primitives for `SharpCoreDB`.
## Installation
```bash
-dotnet add package SharpCoreDB.EventSourcing --version 2.1.1
+dotnet add package SharpCoreDB.EventSourcing --version 2.1.2
```
## Documentation
diff --git a/src/SharpCoreDB.EventSourcing/SharpCoreDB.EventSourcing.csproj b/src/SharpCoreDB.EventSourcing/SharpCoreDB.EventSourcing.csproj
index a08a8293..6be3ed92 100644
--- a/src/SharpCoreDB.EventSourcing/SharpCoreDB.EventSourcing.csproj
+++ b/src/SharpCoreDB.EventSourcing/SharpCoreDB.EventSourcing.csproj
@@ -14,7 +14,7 @@
$(NoWarn);1591;SA1101;SA1633;SA1309SharpCoreDB.EventSourcing
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.EventSourcing
@@ -27,7 +27,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) with optimistic-concurrency conditional appends - IEventStore.TryAppendEventsAsync / TryAppendEventAsync with an expectedVersion, returning ConditionalAppendResult (Conflict carries the expected and actual stream lengths). This is the append-only ledger's compare-and-set primitive: a writer that lost the race is told so and nothing is written.
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) with optimistic-concurrency conditional appends - IEventStore.TryAppendEventsAsync / TryAppendEventAsync with an expectedVersion, returning ConditionalAppendResult (Conflict carries the expected and actual stream lengths). This is the append-only ledger's compare-and-set primitive: a writer that lost the race is told so and nothing is written.
diff --git a/src/SharpCoreDB.Extensions/NuGet.README.md b/src/SharpCoreDB.Extensions/NuGet.README.md
index 459de356..bfb88251 100644
--- a/src/SharpCoreDB.Extensions/NuGet.README.md
+++ b/src/SharpCoreDB.Extensions/NuGet.README.md
@@ -81,7 +81,7 @@ var userId = connection.QuerySingle(
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Extensions --version 2.1.1
+dotnet add package SharpCoreDB.Extensions --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+
diff --git a/src/SharpCoreDB.Extensions/SharpCoreDB.Extensions.csproj b/src/SharpCoreDB.Extensions/SharpCoreDB.Extensions.csproj
index f04347a2..b79bf528 100644
--- a/src/SharpCoreDB.Extensions/SharpCoreDB.Extensions.csproj
+++ b/src/SharpCoreDB.Extensions/SharpCoreDB.Extensions.csproj
@@ -8,13 +8,13 @@
SharpCoreDB.Extensions
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.ExtensionsExtensions for SharpCoreDB including Dapper integration and ASP.NET Core health checks. Built for .NET 11 with C# 15. Supports Windows, Linux, macOS, Android, iOS, and IoT/embedded devices with platform-specific optimizations.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdsharpcoredb;database;dapper;healthchecks;extensions;net11;csharp15;android;ios;mobile;iot;arm64;x64MIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.Functional.Dapper/SharpCoreDB.Functional.Dapper.csproj b/src/SharpCoreDB.Functional.Dapper/SharpCoreDB.Functional.Dapper.csproj
index 6bb55a39..4329bf28 100644
--- a/src/SharpCoreDB.Functional.Dapper/SharpCoreDB.Functional.Dapper.csproj
+++ b/src/SharpCoreDB.Functional.Dapper/SharpCoreDB.Functional.Dapper.csproj
@@ -7,13 +7,13 @@
enableSharpCoreDB.Functional.Dapper
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Functional.DapperDapper adapter for SharpCoreDB.Functional with zero-dependency Option/Fin APIs and fluent functional composition for .NET 11 and C# 15.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdsharpcoredb;functional;dapper;option;fin;net11;csharp15MIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.Functional.EntityFrameworkCore/SharpCoreDB.Functional.EntityFrameworkCore.csproj b/src/SharpCoreDB.Functional.EntityFrameworkCore/SharpCoreDB.Functional.EntityFrameworkCore.csproj
index 221287e1..f058db7b 100644
--- a/src/SharpCoreDB.Functional.EntityFrameworkCore/SharpCoreDB.Functional.EntityFrameworkCore.csproj
+++ b/src/SharpCoreDB.Functional.EntityFrameworkCore/SharpCoreDB.Functional.EntityFrameworkCore.csproj
@@ -7,13 +7,13 @@
enableSharpCoreDB.Functional.EntityFrameworkCore
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Functional.EntityFrameworkCoreEntity Framework Core adapter for SharpCoreDB.Functional with zero-dependency Option/Fin APIs and fluent functional composition for .NET 11 and C# 15.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdsharpcoredb;functional;entityframeworkcore;efcore;option;fin;net11;csharp15MIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.Functional.Linq2DB/SharpCoreDB.Functional.Linq2DB.csproj b/src/SharpCoreDB.Functional.Linq2DB/SharpCoreDB.Functional.Linq2DB.csproj
index b920ac20..eda09cd8 100644
--- a/src/SharpCoreDB.Functional.Linq2DB/SharpCoreDB.Functional.Linq2DB.csproj
+++ b/src/SharpCoreDB.Functional.Linq2DB/SharpCoreDB.Functional.Linq2DB.csproj
@@ -7,13 +7,13 @@
enableSharpCoreDB.Functional.Linq2DB
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Functional.Linq2DBProduction-ready linq2db adapter for SharpCoreDB.Functional. Zero-dependency Option/Fin/Seq APIs, compile-time LINQ safety, BulkCopy batching, full type mappings (ULID/GUID), and low-overhead queries. Perfect for AI/agentic and GraphRAG workloads on .NET 11 / C# 15.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdsharpcoredb;functional;linq2db;linq;option;fin;net11;csharp15;aotMIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.Functional/SharpCoreDB.Functional.csproj b/src/SharpCoreDB.Functional/SharpCoreDB.Functional.csproj
index d633a3bd..4970d5ea 100644
--- a/src/SharpCoreDB.Functional/SharpCoreDB.Functional.csproj
+++ b/src/SharpCoreDB.Functional/SharpCoreDB.Functional.csproj
@@ -7,13 +7,13 @@
enableSharpCoreDB.Functional
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.FunctionalFunctional facade for SharpCoreDB with zero-dependency Option, Fin, Seq, and Unit types, fluent composition, and low-allocation wrappers for .NET 11 and C# 15.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdsharpcoredb;functional;option;result;fin;monad;net11;csharp15MIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.Graph.Advanced/NuGet.README.md b/src/SharpCoreDB.Graph.Advanced/NuGet.README.md
index 54c59259..8db538cf 100644
--- a/src/SharpCoreDB.Graph.Advanced/NuGet.README.md
+++ b/src/SharpCoreDB.Graph.Advanced/NuGet.README.md
@@ -28,7 +28,7 @@ Advanced graph analytics and GraphRAG package for `SharpCoreDB`.
## Installation
```bash
-dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.1
+dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.2
```
## Documentation
diff --git a/src/SharpCoreDB.Graph.Advanced/SharpCoreDB.Graph.Advanced.csproj b/src/SharpCoreDB.Graph.Advanced/SharpCoreDB.Graph.Advanced.csproj
index c0e7d07f..03813cf9 100644
--- a/src/SharpCoreDB.Graph.Advanced/SharpCoreDB.Graph.Advanced.csproj
+++ b/src/SharpCoreDB.Graph.Advanced/SharpCoreDB.Graph.Advanced.csproj
@@ -6,7 +6,7 @@
previewSharpCoreDB.Graph.AdvancedSharpCoreDB.Graph.Advanced
- 2.1.1
+ 2.1.2SharpCoreDB.Graph.AdvancedMPCoreDeveloperMPCoreDeveloper
diff --git a/src/SharpCoreDB.Graph/NuGet.README.md b/src/SharpCoreDB.Graph/NuGet.README.md
index 303906b8..4414918e 100644
--- a/src/SharpCoreDB.Graph/NuGet.README.md
+++ b/src/SharpCoreDB.Graph/NuGet.README.md
@@ -53,7 +53,7 @@ A* pathfinding and graph algorithms **30-50% faster than alternatives** with pur
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Graph --version 2.1.1
+dotnet add package SharpCoreDB.Graph --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+
diff --git a/src/SharpCoreDB.Graph/SharpCoreDB.Graph.csproj b/src/SharpCoreDB.Graph/SharpCoreDB.Graph.csproj
index 2a6ca1ad..2537683d 100644
--- a/src/SharpCoreDB.Graph/SharpCoreDB.Graph.csproj
+++ b/src/SharpCoreDB.Graph/SharpCoreDB.Graph.csproj
@@ -13,7 +13,7 @@
$(NoWarn);1591;SA1101;SA1633;SA1309SharpCoreDB.Graph
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Graph
@@ -26,7 +26,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.HybridSearch/SharpCoreDB.HybridSearch.csproj b/src/SharpCoreDB.HybridSearch/SharpCoreDB.HybridSearch.csproj
index 24acf2cc..e06ccfe7 100644
--- a/src/SharpCoreDB.HybridSearch/SharpCoreDB.HybridSearch.csproj
+++ b/src/SharpCoreDB.HybridSearch/SharpCoreDB.HybridSearch.csproj
@@ -12,7 +12,7 @@
$(NoWarn);1591;SA1101;SA1633;SA1309SharpCoreDB.HybridSearch
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.HybridSearch
@@ -25,7 +25,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview). Fuses the BM25 lexical leg and the vector leg by RANK (never by raw score), with per-leg ranks carried on every hit.
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview). Fuses the BM25 lexical leg and the vector leg by RANK (never by raw score), with per-leg ranks carried on every hit.
diff --git a/src/SharpCoreDB.Identity/SharpCoreDB.Identity.csproj b/src/SharpCoreDB.Identity/SharpCoreDB.Identity.csproj
index d74512cf..517af018 100644
--- a/src/SharpCoreDB.Identity/SharpCoreDB.Identity.csproj
+++ b/src/SharpCoreDB.Identity/SharpCoreDB.Identity.csproj
@@ -8,7 +8,7 @@
trueSharpCoreDB.Identity
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Identity
diff --git a/src/SharpCoreDB.Projections/NuGet.README.md b/src/SharpCoreDB.Projections/NuGet.README.md
index 61a47923..c2bfcc02 100644
--- a/src/SharpCoreDB.Projections/NuGet.README.md
+++ b/src/SharpCoreDB.Projections/NuGet.README.md
@@ -28,7 +28,7 @@ Projection primitives for `SharpCoreDB.EventSourcing`.
## Installation
```bash
-dotnet add package SharpCoreDB.Projections --version 2.1.1
+dotnet add package SharpCoreDB.Projections --version 2.1.2
```
## Documentation
diff --git a/src/SharpCoreDB.Projections/SharpCoreDB.Projections.csproj b/src/SharpCoreDB.Projections/SharpCoreDB.Projections.csproj
index 58ab88b5..ccfaacd7 100644
--- a/src/SharpCoreDB.Projections/SharpCoreDB.Projections.csproj
+++ b/src/SharpCoreDB.Projections/SharpCoreDB.Projections.csproj
@@ -11,7 +11,7 @@
$(NoWarn);1591;SA1101;SA1633;SA1309SharpCoreDB.Projections
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Projections
@@ -24,7 +24,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.Provider.Sync/NuGet.README.md b/src/SharpCoreDB.Provider.Sync/NuGet.README.md
index ec7fd884..d720326d 100644
--- a/src/SharpCoreDB.Provider.Sync/NuGet.README.md
+++ b/src/SharpCoreDB.Provider.Sync/NuGet.README.md
@@ -71,7 +71,7 @@ Console.WriteLine($"Synced {result.TotalChangesDownloaded} changes from cloud");
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Provider.Sync --version 2.1.1
+dotnet add package SharpCoreDB.Provider.Sync --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+, Dotmim.Sync.Core v1.3.0+
diff --git a/src/SharpCoreDB.Provider.Sync/SharpCoreDB.Provider.Sync.csproj b/src/SharpCoreDB.Provider.Sync/SharpCoreDB.Provider.Sync.csproj
index 072454ac..2eb8f1f2 100644
--- a/src/SharpCoreDB.Provider.Sync/SharpCoreDB.Provider.Sync.csproj
+++ b/src/SharpCoreDB.Provider.Sync/SharpCoreDB.Provider.Sync.csproj
@@ -9,7 +9,7 @@
SharpCoreDB.Provider.Sync
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Provider.Sync
diff --git a/src/SharpCoreDB.Provider.YesSql/NuGet.README.md b/src/SharpCoreDB.Provider.YesSql/NuGet.README.md
index fcabf070..39a155e4 100644
--- a/src/SharpCoreDB.Provider.YesSql/NuGet.README.md
+++ b/src/SharpCoreDB.Provider.YesSql/NuGet.README.md
@@ -36,7 +36,7 @@ YesSql ORM integration with SharpCoreDB's encryption and performance for documen
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Provider.YesSql --version 2.1.1
+dotnet add package SharpCoreDB.Provider.YesSql --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+, YesSql.Core v5.4.7+
diff --git a/src/SharpCoreDB.Provider.YesSql/SharpCoreDB.Provider.YesSql.csproj b/src/SharpCoreDB.Provider.YesSql/SharpCoreDB.Provider.YesSql.csproj
index cb49012e..a8d61a2d 100644
--- a/src/SharpCoreDB.Provider.YesSql/SharpCoreDB.Provider.YesSql.csproj
+++ b/src/SharpCoreDB.Provider.YesSql/SharpCoreDB.Provider.YesSql.csproj
@@ -8,13 +8,13 @@
SharpCoreDB.Provider.YesSql
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Provider.YesSqlYesSql provider for SharpCoreDB encrypted database engine. Built for .NET 11 with C# 15. Supports Windows, Linux, macOS, Android, iOS, and IoT/embedded devices with platform-specific optimizations.Copyright (c) 2026 MPCoreDeveloper
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.mdsharpcoredb;yessql;database;orm;net11;csharp15;android;ios;mobile;iot;arm64;x64MIThttps://github.com/MPCoreDeveloper/SharpCoreDB
diff --git a/src/SharpCoreDB.Search/SharpCoreDB.Search.csproj b/src/SharpCoreDB.Search/SharpCoreDB.Search.csproj
index e1318f2e..e39aad2f 100644
--- a/src/SharpCoreDB.Search/SharpCoreDB.Search.csproj
+++ b/src/SharpCoreDB.Search/SharpCoreDB.Search.csproj
@@ -12,7 +12,7 @@
$(NoWarn);1591;SA1101;SA1633;SA1309SharpCoreDB.Search
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Search
@@ -25,7 +25,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview). Munarium-inspired lexical stack: a classifying tokenizer (numbers, sign absorption, hyphen compounds), PostgreSQL-16 English stop words, an English word-only stemmer and a BM25 inverted index with positions.
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview). Munarium-inspired lexical stack: a classifying tokenizer (numbers, sign absorption, hyphen compounds), PostgreSQL-16 English stop words, an English word-only stemmer and a BM25 inverted index with positions.
diff --git a/src/SharpCoreDB.Serilog.Sinks/NuGet.README.md b/src/SharpCoreDB.Serilog.Sinks/NuGet.README.md
index 6e811497..f994e3f0 100644
--- a/src/SharpCoreDB.Serilog.Sinks/NuGet.README.md
+++ b/src/SharpCoreDB.Serilog.Sinks/NuGet.README.md
@@ -53,7 +53,7 @@ Log.Error(ex, "An error occurred");
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.Serilog.Sinks --version 2.1.1
+dotnet add package SharpCoreDB.Serilog.Sinks --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+, Serilog v2.13+
diff --git a/src/SharpCoreDB.Serilog.Sinks/SharpCoreDB.Serilog.Sinks.csproj b/src/SharpCoreDB.Serilog.Sinks/SharpCoreDB.Serilog.Sinks.csproj
index 58e19c1d..3bff6085 100644
--- a/src/SharpCoreDB.Serilog.Sinks/SharpCoreDB.Serilog.Sinks.csproj
+++ b/src/SharpCoreDB.Serilog.Sinks/SharpCoreDB.Serilog.Sinks.csproj
@@ -7,7 +7,7 @@
SharpCoreDB.Serilog.Sinks
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.Serilog.Sinks
@@ -24,7 +24,7 @@
falsetruesnupkg
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.Server.Core/BinaryProtocolHandler.cs b/src/SharpCoreDB.Server.Core/BinaryProtocolHandler.cs
index 1d7b9122..9cb4ecc7 100644
--- a/src/SharpCoreDB.Server.Core/BinaryProtocolHandler.cs
+++ b/src/SharpCoreDB.Server.Core/BinaryProtocolHandler.cs
@@ -5,6 +5,7 @@
using Microsoft.Extensions.Logging;
using SharpCoreDB.Server.Core;
+using SharpCoreDB.Services;
using SharpCoreDB.Server.Core.Catalog;
using SharpCoreDB.Server.Core.Observability;
using SharpCoreDB.Server.Core.Security;
@@ -1118,27 +1119,30 @@ private static string TranslateDollarParameters(string sql)
}
var sb = new StringBuilder(sql.Length);
- bool inString = false;
- char stringChar = '\0';
for (int i = 0; i < sql.Length; i++)
{
char c = sql[i];
- if ((c == '\'' || c == '"') && (i == 0 || sql[i - 1] != '\\'))
+ // A quoted literal is copied verbatim and never translated. SqlLiteralScanner is the single quote
+ // definition (a doubled quote is one quote and does not close the literal; a backslash is data), so a
+ // '$1' inside a value stays text here — the previous form exited the literal at the first quote it saw
+ // after a doubled one and rewrote it.
+ if (c is '\'' or '"')
{
- if (!inString)
+ int closing = SqlLiteralScanner.FindClosingQuote(sql, i, out _);
+ if (closing < 0)
{
- inString = true;
- stringChar = c;
- }
- else if (c == stringChar)
- {
- inString = false;
+ sb.Append(sql, i, sql.Length - i); // unterminated literal: take the rest as written
+ break;
}
+
+ sb.Append(sql, i, closing - i + 1);
+ i = closing;
+ continue;
}
- if (!inString && c == '$' && i + 1 < sql.Length && char.IsDigit(sql[i + 1]))
+ if (c == '$' && i + 1 < sql.Length && char.IsDigit(sql[i + 1]))
{
// Skip '$' and all following digits; emit a single '?'.
i++;
diff --git a/src/SharpCoreDB.Server.Core/SharpCoreDB.Server.Core.csproj b/src/SharpCoreDB.Server.Core/SharpCoreDB.Server.Core.csproj
index 0f55a5fa..7fd75ad9 100644
--- a/src/SharpCoreDB.Server.Core/SharpCoreDB.Server.Core.csproj
+++ b/src/SharpCoreDB.Server.Core/SharpCoreDB.Server.Core.csproj
@@ -9,7 +9,7 @@
SharpCoreDB.Server.Core
- 2.1.1
+ 2.1.2Core server infrastructure for SharpCoreDB network server
@@ -26,7 +26,7 @@
SharpCoreDB.jpgtruesnupkg
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.Server.Protocol/SharpCoreDB.Server.Protocol.csproj b/src/SharpCoreDB.Server.Protocol/SharpCoreDB.Server.Protocol.csproj
index 5e77c1b3..e8d4871c 100644
--- a/src/SharpCoreDB.Server.Protocol/SharpCoreDB.Server.Protocol.csproj
+++ b/src/SharpCoreDB.Server.Protocol/SharpCoreDB.Server.Protocol.csproj
@@ -9,7 +9,7 @@
SharpCoreDB.Server.Protocol
- 2.1.1
+ 2.1.2Network protocol definitions for SharpCoreDB server (gRPC, binary, HTTP)
@@ -26,7 +26,7 @@
SharpCoreDB.jpgtruesnupkg
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): net11.0-only (C# 15 preview) release line - 2,897 tests green across 16 suites; the fixed-width inline capacity ships at 16 by default on both storage paths (batched multi-row INSERT measured +12 to +19 percent); the overflow arena writes a row's payloads in one append instead of one file open per value; and in-place UPDATE/DELETE with deferred index maintenance put the fixed-width fair-PK layout ahead of SQLite on UPDATE (0.8x) and DELETE (0.5x). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md
diff --git a/src/SharpCoreDB.Server/NuGet.README.md b/src/SharpCoreDB.Server/NuGet.README.md
index c08c79b5..d4ca8640 100644
--- a/src/SharpCoreDB.Server/NuGet.README.md
+++ b/src/SharpCoreDB.Server/NuGet.README.md
@@ -35,7 +35,7 @@ Network database server package for `SharpCoreDB`.
## Installation
```bash
-dotnet add package SharpCoreDB.Server.Core --version 2.1.1
+dotnet add package SharpCoreDB.Server.Core --version 2.1.2
```
## Documentation
diff --git a/src/SharpCoreDB.Server/README.md b/src/SharpCoreDB.Server/README.md
index b1515812..c4b22fb6 100644
--- a/src/SharpCoreDB.Server/README.md
+++ b/src/SharpCoreDB.Server/README.md
@@ -38,8 +38,8 @@ docker run -p 5001:5001 -p 8443:8443 ghcr.io/mpcoredeveloper/sharpcoredb-server:
The packable server-side libraries are:
```bash
-dotnet add package SharpCoreDB.Server.Core --version 2.1.0-RC.3
-dotnet add package SharpCoreDB.Server.Protocol --version 2.1.0-RC.3
+dotnet add package SharpCoreDB.Server.Core --version 2.1.2
+dotnet add package SharpCoreDB.Server.Protocol --version 2.1.2
```
## Documentation
diff --git a/src/SharpCoreDB.Server/SharpCoreDB.Server.csproj b/src/SharpCoreDB.Server/SharpCoreDB.Server.csproj
index 5ebecfd2..937420c0 100644
--- a/src/SharpCoreDB.Server/SharpCoreDB.Server.csproj
+++ b/src/SharpCoreDB.Server/SharpCoreDB.Server.csproj
@@ -19,7 +19,7 @@
sharpcoredb-serverSharpCoreDB Network ServerSharpCoreDB network database server with gRPC and HTTP support
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBCopyright (c) 2026 MPCoreDeveloper
diff --git a/src/SharpCoreDB.VectorSearch/NuGet.README.md b/src/SharpCoreDB.VectorSearch/NuGet.README.md
index 5964c307..3acee588 100644
--- a/src/SharpCoreDB.VectorSearch/NuGet.README.md
+++ b/src/SharpCoreDB.VectorSearch/NuGet.README.md
@@ -54,7 +54,7 @@ Semantic search and similarity matching **50-100x faster than SQLite** using HNS
## 📦 Installation
```bash
-dotnet add package SharpCoreDB.VectorSearch --version 2.1.1
+dotnet add package SharpCoreDB.VectorSearch --version 2.1.2
```
**Requires:** SharpCoreDB v1.9.5+
diff --git a/src/SharpCoreDB.VectorSearch/SharpCoreDB.VectorSearch.csproj b/src/SharpCoreDB.VectorSearch/SharpCoreDB.VectorSearch.csproj
index c2a2944f..a7af6e64 100644
--- a/src/SharpCoreDB.VectorSearch/SharpCoreDB.VectorSearch.csproj
+++ b/src/SharpCoreDB.VectorSearch/SharpCoreDB.VectorSearch.csproj
@@ -15,7 +15,7 @@
SharpCoreDB.VectorSearch
- 2.1.1
+ 2.1.2MPCoreDeveloperSharpCoreDBSharpCoreDB.VectorSearch
@@ -28,7 +28,7 @@
gitSharpCoreDB.jpgNuGet.README.md
- v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): Munarium-inspired native C# features - DiskAnn index type, content-verified artifacts (canonical manifest + SHA256 artifact_id + verification), hybrid fusion weights, C# 15 record-based BuildResult for strict error handling. All changes are pure managed .NET 11 / C# 15 on the release/v2.1.0.0 branch. See docs/Vectors/MUNARIUM_INSPIRATION_PLAN.md.
+ v2.1.2 (2026-10-26): patch release with no API changes - a bound text parameter is no longer re-scanned as SQL text, so a value holding an apostrophe, a doubled quote, a backslash, a comma or a parenthesis now round-trips exactly instead of landing in the neighbouring column or failing with "Mismatched parentheses in VALUES clause"; full detail in docs/CHANGELOG.md. Previous: v2.1.1 (2026-10-25): patch release with no API changes. A float[] parameter now reaches a VECTOR(n) column as a vector instead of the literal text "System.Single[]", and ORDER BY vec_distance_*() LIMIT k now reaches the vector index instead of a full scan. Previous: v2.1.0 (2026-10-10): Munarium-inspired native C# features - DiskAnn index type, content-verified artifacts (canonical manifest + SHA256 artifact_id + verification), hybrid fusion weights, C# 15 record-based BuildResult for strict error handling. All changes are pure managed .NET 11 / C# 15 on the release/v2.1.0.0 branch. See docs/Vectors/MUNARIUM_INSPIRATION_PLAN.md.
diff --git a/src/SharpCoreDB/DataStructures/Table.Collation.cs b/src/SharpCoreDB/DataStructures/Table.Collation.cs
index 907595a2..2e985426 100644
--- a/src/SharpCoreDB/DataStructures/Table.Collation.cs
+++ b/src/SharpCoreDB/DataStructures/Table.Collation.cs
@@ -3,6 +3,7 @@ namespace SharpCoreDB.DataStructures;
using System;
using System.Collections.Generic;
using System.Linq;
+using SharpCoreDB.Services;
///
/// ✅ Phase 5: Collation-aware query execution support for WHERE, ORDER BY, DISTINCT, and GROUP BY.
@@ -91,11 +92,10 @@ private static bool EvaluateInOperator(string? rowValue, string? listStr, Collat
if (string.IsNullOrEmpty(listStr))
return false;
- // Parse comma-separated list (remove parentheses, spaces)
- var list = listStr
- .Trim('(', ')')
- .Split(',')
- .Select(s => s.Trim().Trim('\'', '"'))
+ // Parse comma-separated list (remove parentheses, spaces). SplitTopLevelComma keeps a comma inside a
+ // quoted item together, and Unquote reads the item with SQL's rules ('' is one quote).
+ var list = SqlInPredicate.SplitTopLevelComma(listStr.Trim('(', ')'))
+ .Select(s => SqlLiteralScanner.Unquote(s.Trim()))
.ToList();
// Check if rowValue equals any item in the list
@@ -375,10 +375,8 @@ private static bool EvaluateInOperatorWithLocale(string? rowValue, string? listS
if (string.IsNullOrEmpty(listStr))
return false;
- var list = listStr
- .Trim('(', ')')
- .Split(',')
- .Select(s => s.Trim().Trim('\'', '"'))
+ var list = SqlInPredicate.SplitTopLevelComma(listStr.Trim('(', ')'))
+ .Select(s => SqlLiteralScanner.Unquote(s.Trim()))
.ToList();
foreach (var item in list)
diff --git a/src/SharpCoreDB/DataStructures/Table.QueryHelpers.cs b/src/SharpCoreDB/DataStructures/Table.QueryHelpers.cs
index 8c34ce40..ba179230 100644
--- a/src/SharpCoreDB/DataStructures/Table.QueryHelpers.cs
+++ b/src/SharpCoreDB/DataStructures/Table.QueryHelpers.cs
@@ -8,6 +8,7 @@ namespace SharpCoreDB.DataStructures;
using System;
using System.Collections.Generic;
using System.Linq;
+using SharpCoreDB.Services;
///
/// Query helper methods for Table - WHERE clause parsing, row deserialization, result ordering.
@@ -53,7 +54,11 @@ private static bool TryParseSimpleWhereClause(string where, out string column, o
}
ReadOnlySpan colSpan = span[..eq].Trim().Trim("\"[]`".AsSpan());
- ReadOnlySpan valSpan = span[(eq + 1)..].Trim().Trim("'\"".AsSpan());
+
+ // The value stays as written: Unquote below removes exactly one pair of quotes and collapses SQL doubling
+ // (''), so a value holding an apostrophe resolves to the same key it was stored under. Trimming the quote
+ // characters here left the doubling in place and stripped every edge quote (known issue 7).
+ ReadOnlySpan valSpan = span[(eq + 1)..].Trim();
if (colSpan.IsEmpty || valSpan.IsEmpty)
{
@@ -70,7 +75,7 @@ private static bool TryParseSimpleWhereClause(string where, out string column, o
}
column = colSpan.ToString();
- value = valSpan.ToString();
+ value = SqlLiteralScanner.Unquote(valSpan.ToString());
return true;
}
@@ -132,8 +137,8 @@ private static bool TryParseRangeWhereClause(
if (parts.Length == 3)
{
column = parts[0];
- rangeStart = parts[1].Trim('\'', '"');
- rangeEnd = parts[2].Trim('\'', '"');
+ rangeStart = SqlLiteralScanner.Unquote(parts[1].Trim());
+ rangeEnd = SqlLiteralScanner.Unquote(parts[2].Trim());
return true;
}
}
@@ -145,7 +150,7 @@ private static bool TryParseRangeWhereClause(
if (parts.Length == 2)
{
column = parts[0].Trim();
- rangeStart = parts[1].Trim().Trim('\'', '"');
+ rangeStart = SqlLiteralScanner.Unquote(parts[1].Trim());
rangeEnd = "9999999999"; // Large number for upper bound
return true;
}
@@ -159,7 +164,7 @@ private static bool TryParseRangeWhereClause(
if (parts.Length == 2)
{
column = parts[0].Trim();
- rangeStart = parts[1].Trim().Trim('\'', '"');
+ rangeStart = SqlLiteralScanner.Unquote(parts[1].Trim());
rangeEnd = "9999999999";
return true;
}
@@ -173,7 +178,7 @@ private static bool TryParseRangeWhereClause(
{
column = parts[0].Trim();
rangeStart = "0"; // Minimum value
- rangeEnd = parts[1].Trim().Trim('\'', '"');
+ rangeEnd = SqlLiteralScanner.Unquote(parts[1].Trim());
return true;
}
}
@@ -186,7 +191,7 @@ private static bool TryParseRangeWhereClause(
{
column = parts[0].Trim();
rangeStart = "0";
- rangeEnd = parts[1].Trim().Trim('\'', '"');
+ rangeEnd = SqlLiteralScanner.Unquote(parts[1].Trim());
return true;
}
}
diff --git a/src/SharpCoreDB/DataStructures/Table.Scanning.cs b/src/SharpCoreDB/DataStructures/Table.Scanning.cs
index c487f6f2..41dd19f2 100644
--- a/src/SharpCoreDB/DataStructures/Table.Scanning.cs
+++ b/src/SharpCoreDB/DataStructures/Table.Scanning.cs
@@ -225,8 +225,11 @@ private bool EvaluateWhere(Dictionary row, string? where)
// split-based evaluator treats a leading "(" as part of the column name).
where = SqlInPredicate.StripOuterParentheses(where);
- var parts = where.Split(' ', StringSplitOptions.RemoveEmptyEntries);
- if (parts.Length < 3) return true;
+ // Tokenised on top-level whitespace (SqlLiteralScanner): a quoted value keeps its spaces and its doubled
+ // quotes, so 'it''s fine, really' is one token and compares as it''s→it's after Unquote. Splitting on ' '
+ // truncated such a value at its first space and the row could never match (known issue 7's shape).
+ var parts = SqlLiteralScanner.SplitTopLevelWhitespace(where);
+ if (parts.Count < 3) return true;
// ✅ Issue #348: split compound conditions on top-level AND/OR and evaluate each
// operand recursively (via the full single-condition evaluator below). The previous
@@ -266,7 +269,7 @@ private bool EvaluateWhere(Dictionary row, string? where)
// SqlParser evaluator, which now implements SQL-correct semantics (NULL never matches
// LIKE, %/_ patterns, BETWEEN as inclusive range). Previously these fell through the
// switch's default → return true (no filtering) in this directory-mode scan path.
- if (parts.Length >= 2 &&
+ if (parts.Count >= 2 &&
(parts[1].Equals("LIKE", StringComparison.OrdinalIgnoreCase) ||
parts[1].Equals("NOT LIKE", StringComparison.OrdinalIgnoreCase) ||
parts[1].Equals("BETWEEN", StringComparison.OrdinalIgnoreCase)))
@@ -287,7 +290,7 @@ private bool EvaluateWhere(Dictionary row, string? where)
}
// Handle IS NOT NULL (4 tokens: column IS NOT NULL)
- if (parts.Length >= 4
+ if (parts.Count >= 4
&& parts[1].Equals("IS", StringComparison.OrdinalIgnoreCase)
&& parts[2].Equals("NOT", StringComparison.OrdinalIgnoreCase)
&& parts[3].Equals("NULL", StringComparison.OrdinalIgnoreCase))
@@ -303,17 +306,17 @@ private bool EvaluateWhere(Dictionary row, string? where)
}
var op = parts[1];
- var value = parts[2].Trim('"').Trim((char)39);
+ var value = SqlLiteralScanner.Unquote(parts[2]);
// Handle multi-word operators: NOT REGEXP, NOT LIKE, NOT IN (4 tokens: col NOT OP val)
- if (parts.Length >= 4
+ if (parts.Count >= 4
&& parts[1].Equals("NOT", StringComparison.OrdinalIgnoreCase)
&& (parts[2].Equals("REGEXP", StringComparison.OrdinalIgnoreCase)
|| parts[2].Equals("LIKE", StringComparison.OrdinalIgnoreCase)
|| parts[2].Equals("IN", StringComparison.OrdinalIgnoreCase)))
{
op = $"NOT {parts[2].ToUpperInvariant()}";
- value = parts[3].Trim('"').Trim((char)39);
+ value = SqlLiteralScanner.Unquote(parts[3]);
}
if (!row.TryGetValue(columnName, out var rowValue) || rowValue == null)
diff --git a/src/SharpCoreDB/Database/Execution/Database.Batch.cs b/src/SharpCoreDB/Database/Execution/Database.Batch.cs
index ecf832c2..0d15dc6e 100644
--- a/src/SharpCoreDB/Database/Execution/Database.Batch.cs
+++ b/src/SharpCoreDB/Database/Execution/Database.Batch.cs
@@ -111,37 +111,43 @@ public Dictionary ParseValues(ReadOnlySpan valuesClause)
int valueStart = 0;
int valueIndex = 0;
- bool inQuotes = false;
int parenDepth = 0;
int bracketDepth = 0; // bracketed literals ([0.5, 0.25] — a VECTOR value) never split
-
+
for (int i = 0; i < valuesClause.Length && valueIndex < Columns.Count; i++)
{
char c = valuesClause[i];
-
- if (c == '\'' && (i == 0 || valuesClause[i - 1] != '\\'))
+
+ // Skip a whole literal through SqlLiteralScanner (a doubled quote is one quote and does not close
+ // the literal; a backslash is data), so a comma inside a value cannot split the row.
+ if (c == '\'')
{
- inQuotes = !inQuotes;
+ int closing = SqlLiteralScanner.FindClosingQuote(valuesClause, i, out _);
+ if (closing < 0)
+ {
+ break;
+ }
+
+ i = closing;
+ continue;
}
- else if (!inQuotes)
- {
- if (c == '(') parenDepth++;
- else if (c == ')') parenDepth--;
- else if (c == '[') bracketDepth++;
- else if (c == ']' && bracketDepth > 0) bracketDepth--;
- else if (c == ',' && parenDepth == 0 && bracketDepth == 0)
- {
- // ✅ FIX: Strict bounds check before Slice to prevent ArgumentOutOfRangeException
- if (valueStart < i && valueIndex < ColumnTypes.Count)
+
+ if (c == '(') parenDepth++;
+ else if (c == ')') parenDepth--;
+ else if (c == '[') bracketDepth++;
+ else if (c == ']' && bracketDepth > 0) bracketDepth--;
+ else if (c == ',' && parenDepth == 0 && bracketDepth == 0)
{
- var valueSpan = valuesClause.Slice(valueStart, i - valueStart).Trim();
- var value = ParseValueFast(valueSpan, ColumnTypes[valueIndex]);
- row[Columns[valueIndex]] = value ?? DBNull.Value;
- }
-
- valueStart = i + 1;
- valueIndex++;
- }
+ // ✅ FIX: Strict bounds check before Slice to prevent ArgumentOutOfRangeException
+ if (valueStart < i && valueIndex < ColumnTypes.Count)
+ {
+ var valueSpan = valuesClause.Slice(valueStart, i - valueStart).Trim();
+ var value = ParseValueFast(valueSpan, ColumnTypes[valueIndex]);
+ row[Columns[valueIndex]] = value ?? DBNull.Value;
+ }
+
+ valueStart = i + 1;
+ valueIndex++;
}
}
@@ -178,26 +184,35 @@ public object[] ParseValuesToArray(ReadOnlySpan valuesClause)
int valueStart = 0;
int valueIndex = 0;
- bool inQuotes = false;
int parenDepth = 0;
+ int bracketDepth = 0;
for (int i = 0; i < valuesClause.Length && valueIndex < Columns.Count; i++)
{
char c = valuesClause[i];
- if (IsQuoteChar(c, i, valuesClause))
- {
- inQuotes = !inQuotes;
- }
- else if (!inQuotes)
+ // Whole literals are skipped (SqlLiteralScanner is the single quote definition), so a comma or a
+ // parenthesis inside a value cannot end it — and a bracketed VECTOR literal stays together.
+ if (c == '\'')
{
- if (c == '(') parenDepth++;
- else if (c == ')') parenDepth--;
- else if (c == ',' && parenDepth == 0)
+ int closing = SqlLiteralScanner.FindClosingQuote(valuesClause, i, out _);
+ if (closing < 0)
{
- CommitParsedValue(valuesClause, i, ref valueStart, valueIndex, values);
- valueIndex++;
+ break;
}
+
+ i = closing;
+ continue;
+ }
+
+ if (c == '(') parenDepth++;
+ else if (c == ')') parenDepth--;
+ else if (c == '[') bracketDepth++;
+ else if (c == ']' && bracketDepth > 0) bracketDepth--;
+ else if (c == ',' && parenDepth == 0 && bracketDepth == 0)
+ {
+ CommitParsedValue(valuesClause, i, ref valueStart, valueIndex, values);
+ valueIndex++;
}
}
@@ -220,9 +235,6 @@ public object[] ParseValuesToArray(ReadOnlySpan valuesClause)
return values;
}
- private static bool IsQuoteChar(char c, int i, ReadOnlySpan clause)
- => c == '\'' && (i == 0 || clause[i - 1] != '\\');
-
private void CommitParsedValue(ReadOnlySpan valuesClause, int commaIndex, ref int valueStart, int valueIndex, object[] values)
{
if (valueStart < commaIndex && valueIndex < ColumnTypes.Count)
@@ -242,11 +254,18 @@ private void CommitParsedValue(ReadOnlySpan valuesClause, int commaIndex,
{
if (value.IsEmpty || value.Equals("NULL", StringComparison.OrdinalIgnoreCase))
return null;
-
- // Remove quotes if present
+
+ // Remove one pair of quotes if present, and collapse SQL quote doubling ('it''s' → it's) — the same
+ // literal syntax SqlLiteralScanner and SqlParser.ParseValue read.
+ string? unescaped = null;
if (value.Length >= 2 && value[0] == '\'' && value[^1] == '\'')
{
value = value[1..^1];
+ if (value.IndexOf("''") >= 0)
+ {
+ unescaped = value.ToString().Replace("''", "'", StringComparison.Ordinal);
+ value = unescaped.AsSpan();
+ }
}
return type switch
@@ -1882,7 +1901,11 @@ private static List> RowsToDictionaryList(SharpCoreDB
var valuesStart = rest.IndexOf("VALUES", StringComparison.OrdinalIgnoreCase) + "VALUES".Length;
var valuesStr = rest[valuesStart..].Trim().TrimStart('(').TrimEnd(')');
- var values = SqlInPredicate.SplitTopLevelComma(valuesStr).Select(v => v.Trim().Trim('\'')).ToList();
+
+ // Keep the quotes: SqlParser.ParseValue reads literal syntax (one pair off, '' collapsed). Trimming
+ // them here stripped every edge quote and left the doubling in place, so a value ending in a quote
+ // lost it — known issue 7's shape.
+ var values = SqlInPredicate.SplitTopLevelComma(valuesStr).Select(v => v.Trim()).ToList();
Dictionary row = [];
var table = tables[tableName];
diff --git a/src/SharpCoreDB/DatabaseExtensions.cs b/src/SharpCoreDB/DatabaseExtensions.cs
index f51f87a0..ca1618b7 100644
--- a/src/SharpCoreDB/DatabaseExtensions.cs
+++ b/src/SharpCoreDB/DatabaseExtensions.cs
@@ -1533,7 +1533,7 @@ private bool EvaluateSingleCondition(Dictionary row, string cond
if (valueStr.StartsWith('\'') && valueStr.EndsWith('\''))
{
- valueStr = valueStr[1..^1];
+ valueStr = SqlLiteralScanner.Unquote(valueStr);
}
if (rowValue is int intVal && int.TryParse(valueStr, out var intCompare))
@@ -1584,7 +1584,7 @@ private static object ParseValue(string valueStr)
if ((valueStr.StartsWith('\'') && valueStr.EndsWith('\'')) ||
(valueStr.StartsWith('"') && valueStr.EndsWith('"')))
{
- return valueStr[1..^1];
+ return SqlLiteralScanner.Unquote(valueStr);
}
if (int.TryParse(valueStr, out var intVal))
diff --git a/src/SharpCoreDB/NuGet.README.md b/src/SharpCoreDB/NuGet.README.md
index c8ca961a..56cfb63f 100644
--- a/src/SharpCoreDB/NuGet.README.md
+++ b/src/SharpCoreDB/NuGet.README.md
@@ -1,8 +1,8 @@
-# SharpCoreDB 2.1.1 — Performance-First Database Engine
+# SharpCoreDB 2.1.2 — Performance-First Database Engine
**High-Performance Embedded AND Networked Database for .NET 11 (C# 15 preview)**
-> **This is the 2.1.x release line (current: 2.1.1)** — it targets **net11.0 only** with
+> **This is the 2.1.x release line (current: 2.1.2)** — it targets **net11.0 only** with
> **C# 15 preview** (`LangVersion=preview`). The net10.0 / C# 14 packages are the v2.0 stable
> line, parked on `release/v2.0`. See `docs/net11/UPGRADE.md` for what the .NET 11 line adds.
@@ -14,6 +14,49 @@ SharpCoreDB is a modern, encrypted, file-based database engine with SQL support,
[](https://opensource.org/licenses/MIT)
[](https://sonarcloud.io/dashboard?id=MPCoreDeveloper_SharpCoreDB)
+## What's New in 2.1.2
+
+**A data-correctness patch with no API changes** — a bound text parameter now reaches storage exactly as
+you passed it:
+
+- **A bound string holding an apostrophe, a doubled quote or a backslash is no longer re-scanned as SQL.**
+ `ExecuteSQL(sql, parameters)` does not bind at the engine level: it renders each value into the
+ statement text and hands that text to the ordinary parser, so the formatter and the statement scanners
+ have to agree about escaping. They disagreed — the `VALUES` scanner dropped the quote of a doubled pair
+ and let a backslash suppress a quote toggle — and the damage was silent rather than an error: a passage
+ written through a parameter could land in the **neighbouring column**, and a value ending in a backslash
+ could not be written at all. One scanner (`Services/SqlLiteralScanner.cs`) now owns SQL's and SQLite's
+ rules and every literal reader on the statement path uses it: the `VALUES` / `SET` splitters, the
+ matching-parenthesis scan, all four parameter extractors, the batch `INSERT` parsers,
+ `FastSqlLexer.ReadString`, the expression tokenizer, the `WHERE` tokenizer — which used to split on
+ spaces and truncate *any* quoted value at its first space — and the server's `$n` → `?` translation.
+
+ ```csharp
+ // 2.1.1: the value landed in the next column, or the statement threw
+ // "Mismatched parentheses in VALUES clause".
+ // 2.1.2: it round-trips byte-for-byte, and so does every Windows path.
+ db.ExecuteSQL("INSERT INTO Docs (Id, Body) VALUES (@id, @body)", new Dictionary
+ {
+ ["@id"] = 1,
+ ["@body"] = @"It's C:\src\app\, see docs\draft.pdf"
+ });
+ ```
+
+- **A parameter holding a single quote no longer stores an empty string.** `ParseValue` removes exactly
+ one quote pair and collapses the doubling; the old strip loop removed every edge quote, so a value of
+ `'` stored `""` and `'it''s'` reached storage as `it''s`. A quoted empty literal (`''`) now stores an
+ empty string, as SQLite does, where a trailing `''` used to become `NULL`.
+- **`UPDATE … SET col = @v` splits at the first `=`**, not at every `=` — a value containing `=` used to
+ drop that assignment silently. A tuple that cannot fit its statement now throws before any row is
+ written, naming the table, the tuple index, both counts and the column list, instead of shifting
+ values right.
+- **Verified on this tree:** core **2,107** tests / 0 failed, VectorSearch **255** / 0, EF Core **116** /
+ 0, linq2db **24** / 0; `SharpCoreDB.CI.slnf` builds clean. A two-file runnable reproduction with the
+ before/after console output ships in `docs/sharpcoredb-known-issues.md` §7, and the consumer that
+ reported it — SharpAgent's code-evidence index, which writes source text and Windows paths as `TEXT` —
+ runs its 144 indexing tests green against this build with no code change.
+- Full list: `docs/CHANGELOG.md` › `[2.1.2]`.
+
## What's New in 2.1.1
**A patch release with no API changes**, cut from the commits since the `v2.1.0` tag:
@@ -250,16 +293,16 @@ db.Flush(); // Persist to disk
## 📦 Installation
```bash
-dotnet add package SharpCoreDB --version 2.1.1
+dotnet add package SharpCoreDB --version 2.1.2
```
**Optional companion packages:**
```bash
-dotnet add package SharpCoreDB.Functional --version 2.1.1
-dotnet add package SharpCoreDB.Functional.Dapper --version 2.1.1
-dotnet add package SharpCoreDB.Functional.EntityFrameworkCore --version 2.1.1
-dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.1
+dotnet add package SharpCoreDB.Functional --version 2.1.2
+dotnet add package SharpCoreDB.Functional.Dapper --version 2.1.2
+dotnet add package SharpCoreDB.Functional.EntityFrameworkCore --version 2.1.2
+dotnet add package SharpCoreDB.Graph.Advanced --version 2.1.2
```
## 🔄 Upgrading from v1.9
@@ -287,10 +330,10 @@ We welcome contributions! Check the repository for contribution guidelines.
---
-**Latest Version:** 2.1.0 (October 2026)
+**Latest Version:** 2.1.2 (October 2026)
**Target:** .NET 11 / C# 15 (preview)
-**Tests:** 2,897 across 16 suites (0 failed, 16 skipped)
-**Status:** ✅ Released — 2.1.0 (net11.0-only performance line)
+**Tests:** core suite 2,107 (0 failed) · VectorSearch 255 · EF Core 116 · linq2db 24
+**Status:** ✅ Released — 2.1.2 (net11.0-only performance line)
**Versioning:** SharpCoreDB now uses 4-part versions (`n.n.n.n`).
diff --git a/src/SharpCoreDB/Optimizations/WhereClauseAnalyzer.cs b/src/SharpCoreDB/Optimizations/WhereClauseAnalyzer.cs
index 37c7027c..7f815951 100644
--- a/src/SharpCoreDB/Optimizations/WhereClauseAnalyzer.cs
+++ b/src/SharpCoreDB/Optimizations/WhereClauseAnalyzer.cs
@@ -111,8 +111,8 @@ public static bool TryParseSimpleNumericWhere(
return false; // Not a numeric column
}
- // Remove quotes if present
- valuePart = valuePart.Trim('\'', '"');
+ // Remove quotes if present (and collapse SQL doubling: '' is one quote)
+ valuePart = SqlLiteralScanner.Unquote(valuePart.Trim());
// Parse operator
SimdWhereFilter.ComparisonOp compOp;
diff --git a/src/SharpCoreDB/Properties/AssemblyInfo.cs b/src/SharpCoreDB/Properties/AssemblyInfo.cs
index fb67bb87..8ec9391b 100644
--- a/src/SharpCoreDB/Properties/AssemblyInfo.cs
+++ b/src/SharpCoreDB/Properties/AssemblyInfo.cs
@@ -8,3 +8,7 @@
// ✅ Phase 1: Allow test assemblies to access internal members for performance testing
[assembly: InternalsVisibleTo("SharpCoreDB.Tests")]
[assembly: InternalsVisibleTo("SharpCoreDB.Benchmarks")]
+
+// The wire protocol translates PostgreSQL-style '$1' markers to '?' before a statement is executed, so it has to
+// use the same literal scanner as the parser (SqlLiteralScanner) rather than carry its own copy of the rules.
+[assembly: InternalsVisibleTo("SharpCoreDB.Server.Core")]
diff --git a/src/SharpCoreDB/Services/Compilation/FastSqlLexer.cs b/src/SharpCoreDB/Services/Compilation/FastSqlLexer.cs
index 00eb6a8f..870aee31 100644
--- a/src/SharpCoreDB/Services/Compilation/FastSqlLexer.cs
+++ b/src/SharpCoreDB/Services/Compilation/FastSqlLexer.cs
@@ -160,16 +160,24 @@ private Token ReadString(char quote)
var start = position;
position++; // Skip opening quote
- while (!IsAtEnd() && Current() != quote)
+ while (!IsAtEnd())
{
- if (Current() == '\\' && Peek(1) == quote)
+ if (Current() != quote)
{
- position += 2; // Skip escaped quote
+ position++;
+ continue;
}
- else
+
+ // A doubled quote is one quote of data and does not close the literal (SQL/SQLite). A backslash is
+ // data as well: it is a legal last character of a value (a Windows path), so it can never escape the
+ // closing quote — the old escape rule consumed past the literal's end for such a value.
+ if (Peek(1) == quote)
{
- position++;
+ position += 2;
+ continue;
}
+
+ break;
}
if (!IsAtEnd()) position++; // Skip closing quote
diff --git a/src/SharpCoreDB/Services/PreparedStatements.cs b/src/SharpCoreDB/Services/PreparedStatements.cs
index 8bd1e328..ca22eae9 100644
--- a/src/SharpCoreDB/Services/PreparedStatements.cs
+++ b/src/SharpCoreDB/Services/PreparedStatements.cs
@@ -98,32 +98,34 @@ public string BindParameters(Dictionary parameters)
}
/// Extracts positions of ? placeholders in SQL.
+ ///
+ /// Quoted literals are skipped as a whole through , the single quote
+ /// definition: a doubled quote is one quote and does not close the literal, and a backslash is data. The
+ /// previous form toggled on every quote whose predecessor was not a backslash, so 'it''s ?' was read
+ /// as "outside the literal" after the doubled quote and its ? counted as a placeholder.
+ ///
private static int[] ExtractParameterPositions(string sql)
{
var positions = new List();
- bool inString = false;
- char stringChar = '\0';
for (int i = 0; i < sql.Length; i++)
{
char c = sql[i];
- // Handle string literals
- if ((c == '\'' || c == '"') && (i == 0 || sql[i - 1] != '\\'))
+ if (c is '\'' or '"')
{
- if (!inString)
- {
- inString = true;
- stringChar = c;
- }
- else if (c == stringChar)
+ int closing = SqlLiteralScanner.FindClosingQuote(sql, i, out _);
+ if (closing < 0)
{
- inString = false;
+ break; // unterminated literal: nothing after it is a placeholder
}
+
+ i = closing;
+ continue;
}
// Count ? outside strings
- if (!inString && c == '?')
+ if (c == '?')
{
positions.Add(i);
}
@@ -136,30 +138,26 @@ private static int[] ExtractParameterPositions(string sql)
private static Dictionary? ExtractNamedParameters(string sql)
{
var parameters = new Dictionary();
- bool inString = false;
- char stringChar = '\0';
for (int i = 0; i < sql.Length; i++)
{
char c = sql[i];
- // Handle string literals
- if ((c == '\'' || c == '"') && (i == 0 || sql[i - 1] != '\\'))
+ // Skip a whole literal (see ExtractParameterPositions for the rule).
+ if (c is '\'' or '"')
{
- if (!inString)
- {
- inString = true;
- stringChar = c;
- }
- else if (c == stringChar)
+ int closing = SqlLiteralScanner.FindClosingQuote(sql, i, out _);
+ if (closing < 0)
{
- inString = false;
+ break;
}
+
+ i = closing;
continue;
}
// Extract @paramName outside strings
- if (!inString && c == '@' && i + 1 < sql.Length && char.IsLetter(sql[i + 1]))
+ if (c == '@' && i + 1 < sql.Length && char.IsLetter(sql[i + 1]))
{
int nameStart = i + 1;
int nameEnd = nameStart;
@@ -176,7 +174,6 @@ private static int[] ExtractParameterPositions(string sql)
}
// Skip past the parameter name (nameEnd is already one past the last character)
- // Note: loop increment will still happen, so we need to account for that
i = nameEnd - 1;
}
}
@@ -394,30 +391,26 @@ internal static string Bind(string sql, Dictionary parameters)
internal static List<(string name, int pos)> ExtractNamedParameterOccurrences(string sql)
{
var occurrences = new List<(string name, int pos)>();
- bool inString = false;
- char stringChar = '\0';
for (int i = 0; i < sql.Length; i++)
{
char c = sql[i];
- // Skip string literals ('...' or "...").
- if ((c == '\'' || c == '"') && (i == 0 || sql[i - 1] != '\\'))
+ // Skip the whole literal (SqlLiteralScanner is the single quote definition — see
+ // ExtractParameterPositions).
+ if (c is '\'' or '"')
{
- if (!inString)
- {
- inString = true;
- stringChar = c;
- }
- else if (c == stringChar)
+ int closing = SqlLiteralScanner.FindClosingQuote(sql, i, out _);
+ if (closing < 0)
{
- inString = false;
+ break;
}
+ i = closing;
continue;
}
- if (inString || c != '@' || i + 1 >= sql.Length || !char.IsLetterOrDigit(sql[i + 1]))
+ if (c != '@' || i + 1 >= sql.Length || !char.IsLetterOrDigit(sql[i + 1]))
{
continue;
}
@@ -440,29 +433,24 @@ internal static string Bind(string sql, Dictionary parameters)
internal static int[] ExtractPositionalParameterPositions(string sql)
{
var positions = new List();
- bool inString = false;
- char stringChar = '\0';
for (int i = 0; i < sql.Length; i++)
{
char c = sql[i];
- if ((c == '\'' || c == '"') && (i == 0 || sql[i - 1] != '\\'))
+ if (c is '\'' or '"')
{
- if (!inString)
- {
- inString = true;
- stringChar = c;
- }
- else if (c == stringChar)
+ int closing = SqlLiteralScanner.FindClosingQuote(sql, i, out _);
+ if (closing < 0)
{
- inString = false;
+ break;
}
+ i = closing;
continue;
}
- if (!inString && c == '?')
+ if (c == '?')
{
positions.Add(i);
}
diff --git a/src/SharpCoreDB/Services/SqlInPredicate.cs b/src/SharpCoreDB/Services/SqlInPredicate.cs
index da860532..3269f817 100644
--- a/src/SharpCoreDB/Services/SqlInPredicate.cs
+++ b/src/SharpCoreDB/Services/SqlInPredicate.cs
@@ -335,7 +335,7 @@ private static string StripSingleValue(string item)
item = item[1..^1];
}
- return item.Trim().Trim('\'', '"');
+ return SqlLiteralScanner.Unquote(item.Trim());
}
///
@@ -382,13 +382,13 @@ private static List ParseValueRows(string listPart)
{
// Tuple row: (v1, v2) — split the inner list on top-level commas.
rows.Add(SplitTopLevel(t[1..^1])
- .Select(v => v.Trim().Trim('\'', '"'))
+ .Select(v => SqlLiteralScanner.Unquote(v.Trim()))
.Where(v => v.Length > 0)
.ToArray());
}
else
{
- rows.Add([t.Trim('\'', '"')]);
+ rows.Add([SqlLiteralScanner.Unquote(t)]);
}
}
@@ -410,25 +410,29 @@ public static List SplitTopLevelComma(string text)
var parts = new List();
int depth = 0;
int bracketDepth = 0;
- bool inString = false;
- char quote = '\0';
int start = 0;
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
- if (inString)
- {
- inString = c != quote; // closing quote exits the string literal
- continue;
- }
+ // A quoted literal is skipped as a whole. SqlLiteralScanner is the single quote definition: a
+ // doubled quote is one quote and does not end the literal, and a backslash is data. The previous
+ // form exited at the first quote it met (`inString = c != quote`), so 'it''s, ok' split mid-value
+ // — the SET-list half of known issue 7.
if (c is '\'' or '"')
{
- inString = true;
- quote = c;
+ int closing = SqlLiteralScanner.FindClosingQuote(text, i, out _);
+ if (closing < 0)
+ {
+ break; // unterminated literal: the rest is one part, exactly as before
+ }
+
+ i = closing;
+ continue;
}
- else if (c == '(')
+
+ if (c == '(')
{
depth++;
}
diff --git a/src/SharpCoreDB/Services/SqlLiteralScanner.cs b/src/SharpCoreDB/Services/SqlLiteralScanner.cs
new file mode 100644
index 00000000..720112c3
--- /dev/null
+++ b/src/SharpCoreDB/Services/SqlLiteralScanner.cs
@@ -0,0 +1,195 @@
+//
+// Copyright (c) 2026 MPCoreDeveloper. All rights reserved.
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace SharpCoreDB.Services;
+
+using System;
+using System.Collections.Generic;
+
+///
+/// The single definition of how SharpCoreDB reads a SQL string literal — used by every scanner on the
+/// statement path (VALUES and SET splitters, matching-parenthesis scan, parameter extraction, the batch
+/// INSERT parsers and the wire-protocol parameter translation).
+///
+/// The rules are SQL's, and therefore SQLite's: inside '…' a doubled quote ('') is one quote and
+/// does not close the literal, and a backslash is ordinary data. SharpCoreDB used to treat a backslash
+/// as an escape and did not implement doubling at all, while
+/// rendered strings with SQL doubling — so a bound value that
+/// contained an apostrophe or ended in a backslash could not round-trip (known issue 7: the value was split at
+/// its own comma and shifted into the next column, or the statement failed with "Mismatched parentheses").
+/// Formatter and scanner now share one convention, so "any string comes back byte-exact" holds.
+///
+///
+/// A backslash can never be an escape here: it is a legal last character of a value (a Windows path), and a
+/// scanner that lets it suppress the closing quote cannot represent that value in a statement at all.
+///
+///
+internal static class SqlLiteralScanner
+{
+ ///
+ /// Finds the closing quote of the literal that begins at (which must be the quote
+ /// itself), honouring quote doubling. Returns the closing quote index, or -1 when the literal is
+ /// unterminated. reports whether the literal contains a doubled quote, so
+ /// callers that slice the content know they must collapse it.
+ ///
+ /// The statement text.
+ /// Index of the opening quote.
+ /// Set when the literal contains '' (or the doubled form of the quote in use).
+ /// The closing quote index, or -1.
+ internal static int FindClosingQuote(ReadOnlySpan text, int start, out bool hasDoubledQuote)
+ {
+ hasDoubledQuote = false;
+ char quote = text[start];
+
+ for (int i = start + 1; i < text.Length; i++)
+ {
+ if (text[i] != quote)
+ {
+ continue;
+ }
+
+ // A doubled quote is one quote of data: it does not close the literal.
+ if (i + 1 < text.Length && text[i + 1] == quote)
+ {
+ hasDoubledQuote = true;
+ i++;
+ continue;
+ }
+
+ return i;
+ }
+
+ return -1;
+ }
+
+ /// String overload of .
+ internal static int FindClosingQuote(string text, int start, out bool hasDoubledQuote)
+ => FindClosingQuote(text.AsSpan(), start, out hasDoubledQuote);
+
+ ///
+ /// Returns the index just past the closing quote of the literal that begins at
+ /// , or -1 when it is unterminated. Callers that only need to know where a
+ /// literal ends (splitters, parenthesis matching, parameter extraction) use this.
+ ///
+ internal static int SkipLiteral(ReadOnlySpan text, int start)
+ {
+ int closing = FindClosingQuote(text, start, out _);
+ return closing < 0 ? -1 : closing + 1;
+ }
+
+ ///
+ /// Splits a condition on whitespace that is not inside a quoted literal, a parenthesized group or a
+ /// bracketed literal, returning the tokens with their text unaltered.
+ ///
+ /// This is what a WHERE evaluator needs: splitting on spaces alone turns
+ /// name = 'it''s fine, really' into three tokens of which the value is 'it''s, so the row can
+ /// never match (and a bracketed vector literal [0.5, 0.25] is split at its comma's space).
+ ///
+ ///
+ /// The condition text.
+ /// The top-level tokens, in order.
+ internal static List SplitTopLevelWhitespace(string text)
+ {
+ var tokens = new List();
+ int depth = 0;
+ int bracketDepth = 0;
+ int start = -1;
+
+ for (int i = 0; i < text.Length; i++)
+ {
+ char c = text[i];
+
+ if (c is '\'' or '"')
+ {
+ if (start < 0)
+ {
+ start = i;
+ }
+
+ int closing = FindClosingQuote(text, i, out _);
+ if (closing < 0)
+ {
+ i = text.Length - 1; // unterminated literal: take the rest as one token
+ continue;
+ }
+
+ i = closing;
+ continue;
+ }
+
+ if (char.IsWhiteSpace(c) && depth == 0 && bracketDepth == 0)
+ {
+ if (start >= 0)
+ {
+ tokens.Add(text[start..i]);
+ start = -1;
+ }
+
+ continue;
+ }
+
+ if (c == '(')
+ {
+ depth++;
+ }
+ else if (c == ')' && depth > 0)
+ {
+ depth--;
+ }
+ else if (c == '[')
+ {
+ bracketDepth++;
+ }
+ else if (c == ']' && bracketDepth > 0)
+ {
+ bracketDepth--;
+ }
+
+ if (start < 0)
+ {
+ start = i;
+ }
+ }
+
+ if (start >= 0)
+ {
+ tokens.Add(text[start..]);
+ }
+
+ return tokens;
+ }
+
+ ///
+ /// Removes exactly one pair of matching surrounding quotes and collapses the doubled quote inside it
+ /// ('it''s' → it's). Text without a matching pair is returned unchanged, so a value that is
+ /// not a literal is never altered. This replaces the earlier Trim('\'') / repeated-strip forms,
+ /// which removed every leading and trailing quote and left the doubling in place.
+ ///
+ /// A literal (with quotes) or a bare value.
+ /// The unquoted value.
+ internal static string Unquote(string value)
+ {
+ if (value.Length < 2)
+ {
+ return value;
+ }
+
+ char quote = value[0];
+ if (quote != '\'' && quote != '"')
+ {
+ return value;
+ }
+
+ if (value[^1] != quote)
+ {
+ return value;
+ }
+
+ var inner = value[1..^1];
+ return quote == '\''
+ ? inner.Replace("''", "'", StringComparison.Ordinal)
+ : inner.Replace("\"\"", "\"", StringComparison.Ordinal);
+ }
+}
diff --git a/src/SharpCoreDB/Services/SqlParser.DML.InsertConflict.cs b/src/SharpCoreDB/Services/SqlParser.DML.InsertConflict.cs
index a8f20e67..41c11dbb 100644
--- a/src/SharpCoreDB/Services/SqlParser.DML.InsertConflict.cs
+++ b/src/SharpCoreDB/Services/SqlParser.DML.InsertConflict.cs
@@ -232,7 +232,9 @@ private static bool EvaluateDoUpdateWhere(
{
var leftCol = literalOpMatch.Groups[1].Value;
var op = literalOpMatch.Groups[2].Value;
- var rightRaw = literalOpMatch.Groups[3].Value.Trim().Trim('\'');
+ // Keep the raw literal: ParseValue removes one pair of quotes and collapses doubling. Trimming quotes
+ // here as well stripped a value that legitimately ends in a quote (known issue 7's shape).
+ var rightRaw = literalOpMatch.Groups[3].Value.Trim();
existingRow.TryGetValue(leftCol, out var leftVal);
var colIdx = table.Columns.IndexOf(leftCol);
var colType = colIdx >= 0 ? table.ColumnTypes[colIdx] : DataType.String;
diff --git a/src/SharpCoreDB/Services/SqlParser.DML.cs b/src/SharpCoreDB/Services/SqlParser.DML.cs
index 3a432609..cf5c72b4 100644
--- a/src/SharpCoreDB/Services/SqlParser.DML.cs
+++ b/src/SharpCoreDB/Services/SqlParser.DML.cs
@@ -383,6 +383,26 @@ private void ExecuteInsert(string sql, IWAL? wal)
}
}
+ // Known issue 7, safety net: a value that splits its literal at a comma used to land in the NEXT column
+ // (SharpAgent saw an embedding column receive "… iterations)") or fail later as an unrelated parse error.
+ // A tuple that produces more values than the statement can consume is malformed, so it is rejected here —
+ // before any row is written — naming the table, the tuple and both counts. Fewer values stays legal:
+ // that is the documented missing-value path, and the column falls to NULL.
+ int expectedValueCount = insertColumns?.Count ?? table.Columns.Count - (skipInternalRowId ? 1 : 0);
+ for (int rowIndex = 0; rowIndex < allRowValues.Count; rowIndex++)
+ {
+ var rowValues = allRowValues[rowIndex];
+ if (rowValues.Count > expectedValueCount)
+ {
+ throw new InvalidOperationException(
+ $"Column count mismatch in INSERT INTO '{tableName}': VALUES tuple {rowIndex} produced " +
+ $"{rowValues.Count} values but {expectedValueCount} can be consumed " +
+ $"({string.Join(", ", insertColumns ?? table.Columns)}). A string literal that ends early " +
+ $"splits the tuple at its own comma; write an embedded quote as a doubled quote ('') " +
+ $"instead of escaping it with a backslash.");
+ }
+ }
+
// Build one row from one VALUES tuple. Shared by the per-row loop and the batched fast path below so
// the two cannot drift apart on value parsing, column mapping or internal-_rowid skipping.
Dictionary BuildRowFromValues(List rowValues)
@@ -629,30 +649,43 @@ private static List> ParseMultiRowInsertValues(string valuesRest)
///
/// Helper: Find matching closing parenthesis, respecting quoted strings.
+ ///
+ /// The quote rule is 's: a doubled quote is one quote and does not close the
+ /// literal, so a value such as 'it''s)' cannot end the tuple early.
+ ///
///
private static int FindMatchingCloseParen(string str, int openParenIdx)
{
int depth = 0;
- bool inQuotes = false;
for (int i = openParenIdx; i < str.Length; i++)
{
char c = str[i];
- // Toggle quote state, respecting escape
- if (c == '\'' && (i == 0 || str[i - 1] != '\\'))
+ // Skip a whole literal, quotes included.
+ if (c == '\'')
{
- inQuotes = !inQuotes;
+ int closing = SqlLiteralScanner.FindClosingQuote(str, i, out _);
+ if (closing < 0)
+ {
+ return -1; // unterminated literal — the tuple cannot be closed
+ }
+
+ i = closing;
+ continue;
}
// Track parenthesis depth only outside quotes
- if (!inQuotes)
+ if (c == '(')
{
- if (c == '(') depth++;
- else if (c == ')')
+ depth++;
+ }
+ else if (c == ')')
+ {
+ depth--;
+ if (depth == 0)
{
- depth--;
- if (depth == 0) return i;
+ return i;
}
}
}
@@ -661,165 +694,103 @@ private static int FindMatchingCloseParen(string str, int openParenIdx)
}
///
- /// ✅ UPDATED: Parses single row INSERT VALUES using modern Span-based approach.
- /// Respects quoted strings and handles escaping correctly.
+ /// Splits one INSERT … VALUES tuple into its element texts.
+ ///
+ /// Structural only: each element is returned as written, quotes and all, and
+ /// is the single reader of literal syntax. The two therefore cannot
+ /// disagree about escaping, which is what known issue 7 was: the formatter doubled quotes while this
+ /// scanner had its own backslash-escape rule, so a bound text value was split at its own comma and shifted
+ /// into the neighbouring column.
+ ///
+ ///
+ /// The rules, pinned by InsertValuesParsingTests:
+ ///
+ /// a comma splits only outside a quoted literal and outside a bracketed literal
+ /// ([0.5, 0.25, 0.125] is one VECTOR value — docs/Vectors/TECHNICAL_SPEC.md §7). Quote
+ /// scanning is 's single definition: a doubled quote is one quote and does not
+ /// close the literal, and a backslash is data;
+ /// every element is Trim()ed;
+ /// an element is a value when it has any text, and a quoted element is a value even when its content is
+ /// empty — (1, '') stores an empty string, as SQLite does. Only a literally absent element is dropped,
+ /// so (1,) yields one value while (1, ) yields an empty one: a missing literal and an
+ /// empty-string literal are different values.
+ ///
+ ///
///
private static List ParseInsertValues(ReadOnlySpan valuesStr)
{
List values = [];
- // (2026-09-16, plan §9 priority 2 lever 1) Literals are sliced out of the statement instead of being
- // appended to a StringBuilder one character at a time. Measured, that per-character build is the parse
- // stage's dominant cost on the 1,000-row-statement shape (3.33 µs/row, ~1 KB/row allocated).
- //
- // The four rules reproduced here are pinned by InsertValuesParsingTests:
- // 1. a quote toggles and is DROPPED, unless the previously *appended* character was a backslash — the
- // check is against the accumulated content, not the source text, so a dropped quote does not update it;
- // 2. a comma splits only outside quotes and outside a bracketed literal;
- // 3. every literal is Trimmed;
- // 4. a literal whose content is empty is not emitted at all, so `(1,)` yields one value while `(1, )`
- // yields an empty one.
- //
- // Rule 2's bracketed-literal clause is what makes a VECTOR value expressible in VALUES at all:
- // `[0.5, 0.25, 0.125, 0.0625]` is one literal, not four (docs/Vectors/TECHNICAL_SPEC.md §7). Without
- // it the first fragment reached ParseVectorValue as "[0.5" and the statement failed as invalid base64.
- //
- // A literal is slice-expressible exactly when at most one quote pair toggles inside it. An interior
- // toggling quote — `'it''s'` → `its` — is *dropped* by rule 1, which no contiguous slice can express, so
- // such a literal falls back to the character loop this method used to run for everything. That loop is
- // kept below as the reference; it is the slow path, not a second interpretation of the rules.
int index = 0;
int length = valuesStr.Length;
while (index < length)
{
- int literalStart = index;
- int contentStart = index;
- int contentEnd = -1;
- int toggles = 0;
+ int elementStart = index;
int bracketDepth = 0;
- bool inQuotes = false;
- char lastAppended = '\0';
- bool terminatedByComma = false;
+ int literalCount = 0;
+ // One pass over the element: whole literals are skipped (a comma inside one is text), a bracketed
+ // literal stays in one piece, and the first top-level comma ends the element.
while (index < length)
{
char c = valuesStr[index];
- if (c == '\'' && lastAppended != '\\')
+ if (c == '\'')
{
- toggles++;
- if (toggles == 1) { contentStart = index + 1; inQuotes = true; }
- else if (toggles == 2) { contentEnd = index; inQuotes = false; }
+ int closing = SqlLiteralScanner.FindClosingQuote(valuesStr, index, out _);
+ if (closing < 0)
+ {
+ // Unterminated literal: the rest of the tuple belongs to this element. The text is still
+ // passed on as written, so nothing is silently truncated here.
+ literalCount++;
+ index = length;
+ break;
+ }
- index++;
- continue; // a dropped quote must not become the last appended character
+ literalCount++;
+ index = closing + 1;
+ continue;
}
- if (!inQuotes)
+ if (c == '[')
{
- if (c == '[')
- {
- bracketDepth++;
- }
- else if (c == ']' && bracketDepth > 0)
- {
- bracketDepth--;
- }
+ bracketDepth++;
}
-
- if (c == ',' && !inQuotes && bracketDepth == 0)
+ else if (c == ']' && bracketDepth > 0)
+ {
+ bracketDepth--;
+ }
+ else if (c == ',' && bracketDepth == 0)
{
- terminatedByComma = true;
break;
}
- lastAppended = c;
index++;
}
- // Rule 4, corrected by the suites: an interior literal is emitted **unconditionally** at its comma —
- // `('', 2)` stores an empty string — and only the *trailing* literal is suppressed when it has no text
- // at all. So `(1,)` yields one value, `(1, )` yields an empty one, and `('')` as the last literal
- // yields none while `('', 2)` yields an empty string.
- // The flag is set where the comma is *seen*; deriving it afterwards from `index < length` was wrong,
- // because a literal whose closing quote is the tuple's last character also ends with index == length.
+ bool terminatedByComma = index < length;
+ var element = valuesStr[elementStart..index];
- if (toggles == 0)
- {
- var literal = valuesStr[literalStart..index];
- if (literal.Length > 0 || terminatedByComma)
- {
- values.Add(literal.Trim().ToString());
- }
- }
- else if (toggles == 2 && contentEnd >= contentStart)
+ // A quoted element IS a value even when its content is empty: `(1, '')` stores an empty string in
+ // SQLite and must here too. Only a literally absent element is dropped, so `(1,)` yields one value
+ // while `(1, )` still yields an empty one — a missing literal and an empty-string literal are
+ // different values. (The previous rule suppressed a *trailing* `''` as well, which made a bound
+ // empty string silently NULL — known issue 7's second half; InsertValuesParsingTests pinned it.)
+ bool hasValue = literalCount > 0 || element.Length > 0;
+
+ if (hasValue || terminatedByComma)
{
- var literal = valuesStr[contentStart..contentEnd];
- if (literal.Length > 0 || terminatedByComma)
- {
- values.Add(literal.Trim().ToString());
- }
- }
- else
- {
- var fallback = BuildLiteralByAppending(valuesStr[literalStart..index], terminatedByComma);
- if (fallback is not null)
- {
- values.Add(fallback);
- }
+ values.Add(element.Trim().ToString());
}
- if (index < length)
+ if (terminatedByComma)
{
index++; // step over the separating comma
}
}
return values;
-
- // The reference implementation, used only for literals a slice cannot express: one with an interior
- // toggling quote, or an unterminated one. It is byte-for-byte the behaviour this method had before the
- // fast path existed, including emitting nothing when the accumulated content is empty — plus the
- // bracketed-literal clause of rule 2, so it cannot disagree with the fast path about a vector literal.
- static string? BuildLiteralByAppending(ReadOnlySpan text, bool terminatedByComma)
- {
- var currentValue = new StringBuilder();
- bool inQuotes = false;
- int bracketDepth = 0;
-
- foreach (char c in text)
- {
- if (c == '\'' && (currentValue.Length == 0 || currentValue[^1] != '\\'))
- {
- inQuotes = !inQuotes;
- continue;
- }
-
- if (!inQuotes)
- {
- if (c == '[')
- {
- bracketDepth++;
- }
- else if (c == ']' && bracketDepth > 0)
- {
- bracketDepth--;
- }
- }
-
- if (c == ',' && !inQuotes && bracketDepth == 0)
- {
- break;
- }
-
- currentValue.Append(c);
- }
-
- // Emitted whenever the literal ended at a comma, even with nothing accumulated (an empty string);
- // suppressed only for an empty trailing literal.
- return currentValue.Length == 0 && !terminatedByComma ? null : currentValue.ToString().Trim();
- }
}
///
@@ -2278,11 +2249,14 @@ private void ExecuteUpdate(string sql, IWAL? wal)
var updates = new Dictionary();
foreach (var setClause in setClauses)
{
- var parts = setClause.Split('=');
- if (parts.Length == 2)
+ // Split at the FIRST '=' only. A bound text value may contain '=' itself (a URL query string, a
+ // source line), and Split('=') produced three parts for those and silently dropped the assignment —
+ // the same class as known issue 7: the value was not the value.
+ int assign = setClause.IndexOf('=');
+ if (assign > 0)
{
- var colName = parts[0].Trim();
- var valueStr = parts[1].Trim();
+ var colName = setClause[..assign].Trim();
+ var valueStr = setClause[(assign + 1)..].Trim();
// Find column type
var colIndex = table.Columns.IndexOf(colName);
diff --git a/src/SharpCoreDB/Services/SqlParser.Helpers.cs b/src/SharpCoreDB/Services/SqlParser.Helpers.cs
index c28ba43f..f3d4141a 100644
--- a/src/SharpCoreDB/Services/SqlParser.Helpers.cs
+++ b/src/SharpCoreDB/Services/SqlParser.Helpers.cs
@@ -162,17 +162,18 @@ private static List ParseWhereColumns(string where)
///
public static object? ParseValue(string val, DataType type)
{
- if (val == "NULL")
+ // A bare NULL keyword is the null value; a quoted 'NULL' is the four-character string. The order matters,
+ // so the literal is unquoted only after this check.
+ if (val.Equals("NULL", StringComparison.OrdinalIgnoreCase))
{
return null;
}
- while (val.Length >= 2 &&
- ((val.StartsWith('\'') && val.EndsWith('\'')) ||
- (val.StartsWith('"') && val.EndsWith('"'))))
- {
- val = val[1..^1];
- }
+ // Exactly one pair of quotes is removed and SQL quote doubling is collapsed ('it''s' → it's), through
+ // SqlLiteralScanner — the same definition every statement scanner uses. The previous form stripped
+ // repeatedly, so a value that ended in a quote lost it (a parameter holding `'` stored an empty string),
+ // and it never collapsed doubling, so 'it''s' reached storage as it''s. Both are known issue 7.
+ val = SqlLiteralScanner.Unquote(val);
try
{
diff --git a/src/SharpCoreDB/Services/SqlParser.PerformanceOptimizations.cs b/src/SharpCoreDB/Services/SqlParser.PerformanceOptimizations.cs
index 0207024c..91d93cf3 100644
--- a/src/SharpCoreDB/Services/SqlParser.PerformanceOptimizations.cs
+++ b/src/SharpCoreDB/Services/SqlParser.PerformanceOptimizations.cs
@@ -395,9 +395,8 @@ private static bool CompareIn(object? value, string valueStr)
if (!valueStr.StartsWith("(") || !valueStr.EndsWith(")"))
return false;
- var items = valueStr[1..^1] // Remove parentheses
- .Split(',')
- .Select(s => s.Trim().Trim('\'', '"'))
+ var items = SqlInPredicate.SplitTopLevelComma(valueStr[1..^1]) // Remove parentheses
+ .Select(s => SqlLiteralScanner.Unquote(s.Trim()))
.ToList();
return items.Contains(value.ToString() ?? string.Empty);
@@ -411,8 +410,8 @@ private static bool CompareLike(object? value, string valueStr)
if (value == null)
return false;
- // Remove quotes
- valueStr = valueStr.Trim('\'', '"');
+ // Remove quotes (and collapse SQL doubling)
+ valueStr = SqlLiteralScanner.Unquote(valueStr);
// Simple LIKE: convert SQL wildcards to regex
var pattern = "^" + System.Text.RegularExpressions.Regex.Escape(valueStr)
diff --git a/src/SharpCoreDB/Services/SqlParserExtensions.cs b/src/SharpCoreDB/Services/SqlParserExtensions.cs
index c23f89da..d4312de5 100644
--- a/src/SharpCoreDB/Services/SqlParserExtensions.cs
+++ b/src/SharpCoreDB/Services/SqlParserExtensions.cs
@@ -321,6 +321,6 @@ private static List