tursodatabase/turso · error · SqliteException

14

14

Error message

SQLite Error {errorCode}: 'unable to open database file'.

What it means

Thrown by NormalizeDataSource when Mode is ReadOnly or ReadWrite and the resolved database file does not exist, with SQLite code 14 (SQLITE_CANTOPEN). The check happens before open: these two modes never create files, so a missing file is reported as 'unable to open database file'. The path is resolved relative to AppContext.BaseDirectory when not rooted, not relative to the process working directory.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteConnection.cs:756

                ? GetSharedMemoryFile(dataSource)
                : ":memory:";
        if (dataSource.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
            return NormalizeUriDataSource(dataSource);

        const string dataDirectory = "|DataDirectory|";
        if (dataSource.StartsWith(dataDirectory, StringComparison.OrdinalIgnoreCase))
        {
            var baseDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string
                                ?? AppContext.BaseDirectory;
            dataSource = Path.Combine(baseDirectory, dataSource[dataDirectory.Length..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
        }

        var filename = Path.IsPathRooted(dataSource)
            ? dataSource
            : Path.Combine(AppContext.BaseDirectory, dataSource);

        if ((options.Mode == SqliteOpenMode.ReadOnly || options.Mode == SqliteOpenMode.ReadWrite) && !File.Exists(filename))
            throw new SqliteException(Properties.Resources.SqliteNativeError(SQLITE_CANTOPEN, "unable to open database file"), SQLITE_CANTOPEN);

        return filename;
    }

    private static string NormalizeUriDataSource(string dataSource)
    {
        var queryStart = dataSource.IndexOf('?', StringComparison.Ordinal);
        var path = queryStart < 0 ? dataSource[5..] : dataSource[5..queryStart];
        var query = queryStart < 0 ? string.Empty : dataSource[(queryStart + 1)..];
        foreach (var part in query.Split('&', StringSplitOptions.RemoveEmptyEntries))
        {
            var pieces = part.Split('=', 2);
            if (!pieces[0].Equals("mode", StringComparison.OrdinalIgnoreCase))
                continue;

            var mode = pieces.Length == 2 ? pieces[1] : string.Empty;
            if (!mode.Equals("ro", StringComparison.OrdinalIgnoreCase)
                && !mode.Equals("rw", StringComparison.OrdinalIgnoreCase)

View on GitHub (pinned to 244cde92a7)

Solutions

  1. Use Mode=ReadWriteCreate (the default) if the database is allowed to be created on first open.
  2. Verify the resolved path exists before connecting: Path.Combine(AppContext.BaseDirectory, relativePath) then File.Exists — remember relative paths resolve against the app base directory, not Environment.CurrentDirectory.
  3. For read-only access to an optional file, deploy or copy the .db file to the publish output (mark it CopyToOutputDirectory in the project file) or use an absolute path.

Example fix

// before
var conn = new SqliteConnection("Data Source=data/app.db;Mode=ReadOnly");
conn.Open(); // throws if app.db missing

// after
var dbPath = Path.Combine(AppContext.BaseDirectory, "data", "app.db");
if (!File.Exists(dbPath))
    throw new FileNotFoundException("Database not deployed", dbPath);
var conn = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
Defensive patterns

Strategy: validation

Validate before calling

var path = Path.IsPathRooted(ds) ? ds : Path.Combine(AppContext.BaseDirectory, ds);
if ((mode is SqliteOpenMode.ReadOnly or SqliteOpenMode.ReadWrite) && !File.Exists(path))
    throw new FileNotFoundException("Database file missing (required by Mode).", path);

Try / catch

try { conn.Open(); }
catch (SqliteException ex) when (ex.SqliteErrorCode == 14)
{ /* log resolved path, verify deployment, or fall back to ReadWriteCreate */ }

Prevention

When it happens

Trigger: Opening 'Data Source=app.db;Mode=ReadOnly' (or Mode=ReadWrite) when app.db is absent at the resolved path; using a relative filename and running from a different working directory, because the binding combines it with AppContext.BaseDirectory; '|DataDirectory|' substitution resolving to a folder without the file.

Common situations: Assuming the file will be created like the default ReadWriteCreate mode does; deploying an app where the .db file was not copied to the output/publish folder; relative-path bugs in test runners (dotnet test runs from bin/Debug) and single-file publishes; wrong casing of the filename on Linux.

Related errors


AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20). Data as JSON: /api/errors/998b6db6d8a45d1c. Report an issue: GitHub.