Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Features

* Add support for opening 'file:' URIs with parameters. [(#234)](https://github.com/dbcli/litecli/pull/234)

## 1.16.0 - 2025-08-16

### Features
Expand Down
18 changes: 12 additions & 6 deletions litecli/sqlexecute.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,26 @@ def connect(self, database=None):
db = database or self.dbname
_logger.debug("Connection DB Params: \n\tdatabase: %r", db)

db_name = os.path.expanduser(db)
db_dir_name = os.path.dirname(os.path.abspath(db_name))
if not os.path.exists(db_dir_name):
raise Exception("Path does not exist: {}".format(db_dir_name))
if db.startswith("file:"):
uri = True
db_name = db
db_filename, *_ = db_name[5:].split("?", 1)
Copy link
Member

Choose a reason for hiding this comment

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

This looks a little brittle. How about using the urlparse() from the urllib.parse module?

We can do

parsed = urlparse(db)
db_filename = parsed.path

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@amjith Thanks for your time!

Sure yeah. tbh, I just wanted to keep the change minimal and stay consistent with the use of string functions elsewhere in the code. I've updated the code. Is this what you had in mind ?

This is a small enough change and I'm happy for you to adapt it in whatever way you think seems best.

else:
uri = False
db_filename = db_name = os.path.expanduser(db)
db_dir_name = os.path.dirname(os.path.abspath(db_filename))
if not os.path.exists(db_dir_name):
raise Exception("Path does not exist: {}".format(db_dir_name))

conn = sqlite3.connect(database=db_name, isolation_level=None)
conn = sqlite3.connect(database=db_name, isolation_level=None, uri=uri)
conn.text_factory = lambda x: x.decode("utf-8", "backslashreplace")
if self.conn:
self.conn.close()

self.conn = conn
# Update them after the connection is made to ensure that it was a
# successful connection.
self.dbname = db
self.dbname = db_filename

def run(self, statement):
"""Execute the sql in the database and return the results. The results
Expand Down
30 changes: 29 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from unittest.mock import patch

import click
import pytest
from click.testing import CliRunner

from litecli.main import cli, LiteCli
from litecli.packages.special.main import COMMANDS as SPECIAL_COMMANDS
from utils import dbtest, run
from utils import dbtest, run, create_db, db_connection

test_dir = os.path.abspath(os.path.dirname(__file__))
project_dir = os.path.dirname(test_dir)
Expand Down Expand Up @@ -330,3 +331,30 @@ def test_get_prompt(mock_datetime):
# 12. Windows path
lc.connect("C:\\Users\\litecli\\litecli_test.db")
assert lc.get_prompt(r"\d") == "C:\\Users\\litecli\\litecli_test.db"


@pytest.mark.parametrize(
"uri, expected_dbname",
[
("file:{tmp_path}/test.db", "{tmp_path}/test.db"),
("file:{tmp_path}/test.db?mode=ro", "{tmp_path}/test.db"),
("file:{tmp_path}/test.db?mode=ro&cache=shared", "{tmp_path}/test.db"),
],
)
def test_file_uri(tmp_path, uri, expected_dbname):
"""
Test that `file:` URIs are correctly handled
ref:
https://docs.python.org/3/library/sqlite3.html#sqlite3-uri-tricks
https://www.sqlite.org/c3ref/open.html#urifilenameexamples
"""
# - ensure db exists
db_path = tmp_path / "test.db"
create_db(db_path)
db_connection(db_path)
uri = uri.format(tmp_path=tmp_path)

lc = LiteCli()
lc.connect(uri)

assert lc.get_prompt(r"\d") == expected_dbname.format(tmp_path=tmp_path)