dbcli / litecli

CLI for SQLite Databases with auto-completion and syntax highlighting
https://litecli.com
BSD 3-Clause "New" or "Revised" License
2.12k stars 68 forks source link

Cannot load extension library #47

Closed zmwangx closed 5 years ago

zmwangx commented 5 years ago

I occasionally use math functions defined in the extension library extension-functions.c. The library needs to be loaded into SQLite.

The official sqlite3 executable allows me to load the extension in two ways (unless compiled with SQLITE_OMIT_LOAD_EXTENSION):

sqlite> SELECT load_extension('/usr/local/opt/sqlite/lib/libsqlitefunctions.dylib');

sqlite> .load '/usr/local/opt/sqlite/lib/libsqlitefunctions.dylib'

litecli however doesn't call sqlite3_enable_load_extension, so extension loading is forbidden:

db> SELECT load_extension('/usr/local/opt/sqlite/lib/libsqlitefunctions.dylib');
not authorized

One solution is to add a .load command like the sqlite3 one. Here's a quick patch:

diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py
index 7307ad2..2e4f622 100644
--- a/litecli/packages/special/dbcommands.py
+++ b/litecli/packages/special/dbcommands.py
@@ -149,3 +149,24 @@ def status(cur, **_):

     footer.append("--------------")
     return [(None, None, "", "\n".join(footer))]
+
+
+@special_command(
+    ".load",
+    ".load",
+    "Load an extension library.",
+    arg_type=PARSED_QUERY,
+    case_sensitive=True,
+)
+def load_extension(cur, arg, **_):
+    # Strip quotes around the path, if any.
+    if (arg.startswith('"') and arg.endswith('"')) or (
+        arg.startswith("'") and arg.endswith("'")
+    ):
+        path = arg[1:-1]
+    else:
+        path = arg
+    conn = cur.connection
+    conn.enable_load_extension(True)
+    conn.load_extension(path)
+    return [(None, None, None, "")]

This way we can load an extension library (with or without quotes):

db> .load /usr/local/opt/sqlite/lib/libsqlitefunctions.dylib
Time: 0.002s
db> .load "/usr/local/opt/sqlite/lib/libsqlitefunctions.dylib"
Time: 0.000s
db> .load '/usr/local/opt/sqlite/lib/libsqlitefunctions.dylib'
Time: 0.000s
db> SELECT SQRT(2);
+--------------------+
| SQRT(2)            |
+--------------------+
| 1.4142135623730951 |
+--------------------+
1 row in set
Time: 0.012s
db> .load /usr/local/opt/sqlite/lib/libxxx.dylib
dlopen(/usr/local/opt/sqlite/lib/libxxx.dylib.dylib, 10): image not found

I can turn the patch into a PR if the new command is deemed acceptable.

(Note that the signature of .load in sqlite3 is .load FILE ?ENTRY?; ~however, Python's sqlite3 doesn't seem to support loading a single entry, and I've never used the optional ENTRY either. Probably okay to keep it simple.~ I was mistaken: ENTRY here is an entry point function, usually something like sqlite3_extension_init, not a specific entry... Still, not really supported by Python's sqlite3 API — it's hard coded as 0.)

amjith commented 5 years ago

Thank you for taking the time to dig into this.

I'd love to see a PR. :smile:

zmwangx commented 5 years ago

49.