Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to auto-load built-in extension #3541

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Conversation

lucydodo
Copy link
Member

@lucydodo lucydodo commented Dec 29, 2023

This patch changes macOS* users can set to automatically load the sqlean extension when loading DB files.
This PR is related to issue #3357.

Detailed Changes and Code-Flow

Changes

Add a checkbox to the 'Extension' tab of the 'PreferencesDialog' for "Automatically load the 'sqlean' extension"

Code-Flow

sqlitebrowser/src/sqlitedb.cpp

Lines 2174 to 2181 in 83649a3

if (Settings::getValue("extensions", "autoload_sqlean_extension").toBool())
{
const QString sqleanExtensionPath = qApp->applicationDirPath() + "/../Extensions/sqlean.dylib";
if (loadExtension(sqleanExtensionPath) == false) {
QMessageBox::warning(nullptr, QApplication::applicationName(), tr("Error loading 'sqlean' extension. Option is disabled."));
Settings::setValue("extensions", "autoload_sqlean_extension", false);
}
}

When loading the DB, get the path to the current app execution if that option is enabled, then locate and load the extension.
(This extension path is determined during packaging, see the following collapse section for more information)

Part of the CI workflow for adding the `sqlean` extension during packaging

- name: Add the 'nalgeon/sqlean' extension to the app bundle
shell: bash
run: |
gh auth login --with-token <<< "${{ inputs.GH_TOKEN }}"
gh release download --pattern "sqlean-macos-arm64.zip" --repo "nalgeon/sqlean"
unzip sqlean-macos-arm64.zip
for TARGET in $(find build -name "DB Browser for SQL*.app" | sed -e 's/ /_/g'); do
TARGET=$(echo $TARGET | sed -e 's/_/ /g')
mkdir "$TARGET/Contents/Extensions"
clang -I /opt/homebrew/opt/db4subsqlitefts@5/include -L /opt/homebrew/opt/db4subsqlitefts@5/lib -fno-common -dynamiclib src/extensions/extension-formats.c -o "$TARGET/Contents/Extensions/formats.dylib"
if [ -f "$TARGET/Contents/Extensions/formats.dylib" ]; then
install_name_tool -id "@executable_path/../Extensions/formats.dylib" "$TARGET/Contents/Extensions/formats.dylib"
ln -s formats.dylib "$TARGET/Contents/Extensions/formats.dylib.dylib"
fi
cp sqlean.dylib "$TARGET/Contents/Extensions/"
if [ -f "$TARGET/Contents/Extensions/sqlean.dylib" ]; then
install_name_tool -id "@executable_path/../Extensions/sqlean.dylib" "$TARGET/Contents/Extensions/sqlean.dylib"
ln -s sqlean.dylib "$TARGET/Contents/Extensions/sqlean.dylib.dylib"
fi
done

and if it fails to load the sqlean extension, it will warn the user and deselect the option.

Screenshot

Screenshot 2023-12-30 at 01 05 39

Test Environment

  • macOS Sonoma 14.2.1 (23C71)
  • Windows 11 Home, 23H2 (22631.2792)
Outdated

[!WARNING]
This change applies to MacOS builds only. Other operating systems are not affected.
Also, when I say testing in a Windows environment, I mean that I have confirmed that this feature is disabled.

Thank you.

*: This PR was changed to support cross-platform not only macOS during review process.
For further information, see the review comments.

@lucydodo
Copy link
Member Author

You can download the test builds from the following link: https://github.com/sqlitebrowser/sqlitebrowser/releases/tag/83649a3-3357

@justinclift
Copy link
Member

The concept sounds good. 😄

With the new Automatically load the 'sqlean' extension option, how does it interact with the option above it Allow loading extensions from SQL code?

Does it only work if the Allow loading extensions from SQL code option is enabled? 😄

@lucydodo
Copy link
Member Author

No, the two options work independently of each other.

@lucydodo
Copy link
Member Author

lucydodo commented Dec 30, 2023

As a note, the CI for Windows keeps failing, and that's because I'm running tests on some CIs right now.
You can ignore it, I'll check it before the final merge. Now fixed.

@justinclift
Copy link
Member

Cool. I don't remember C++ enough to really review this.

Hopefully @MKleusberg or @mgrojo have a few minutes to look at it? The code itself seems pretty short. 😄

@lucydodo
Copy link
Member Author

Thank you for the review. In fact, I'm wondering if there's a better way to get the path fo the extension.

Additionally, issue #3503 enlightened me: we have another extension-formats extension besides sqlean.
Why don't we see it to load that as well? For example, we could change the option to something like
Load built-in extensions on startup, but in this case, the project name sqlean would not be visible. 🤔

@justinclift
Copy link
Member

justinclift commented Dec 31, 2023

Well, "so far" our extension support has been a bit of a mess.

Though stuff improves as people feel motivated to put time into it. 😁

The old PR #1930 ("Create SQLite extensions pack installer for Windows") is still open, and that was created back in 2019.

So it'd be good to get a fairly complete (but simple) approach in place, like this sqlean package.

We can hopefully get it working across all our supported OS's at some point. 😄

For now though, having the sqlean one be (optionally) automatically loaded sounds like a win.

And we should probably have that option turned on by default so things "work out of the box" for the majority of our users.

Allowing other extensions to be (manually) added to that dialog for loading also gives options to the people that need something special.

@lucydodo
Copy link
Member Author

Yes, that makes sense.
Alternatively, we could add the extension-formats extension to the 'sqlean' and build and deploy it. This would eliminate the need for additional options. (However, we could need to confirm this with the original author).

and I'll also have 'sqlean' built-in for Windows builds too in the near future. :)

@justinclift
Copy link
Member

justinclift commented Dec 31, 2023

Alternatively, we could add the extension-formats extension to the 'sqlean' ...

That makes sense too. It might have already been considered, but it doesn't hurt to ask. 😄

@lucydodo
Copy link
Member Author

Yes, I will complete the Windows side first and then ask to the original author. :)

@mgrojo
Copy link
Member

mgrojo commented Dec 31, 2023

I don't oppose to the PR, but ideally, we should be agnostic from the application about what extensions we deploy along with it and automatically load. It should also be OS independent. As I suggested in #1930, we could generate an initial configuration for the list of loaded extensions from what we are installing. One way to do it is to call the application itself in this way after determining the list of extensions:

sqlitebrowser --save-option extensions/list=\path\to\extension1.dll,\path\to\extension2.dll --quit

The truth is that --quit did not save the option for me. But that should be considered a bug and fixed. Maybe there are other ways to do that initial configuration, though.

This will allow users to later remove some of the loaded extensions and not others, if they want to use different versions or implementations.

The directory of the built-in extensions could include a copy of LICENSE-EXTENSIONS for user awareness of included extensions and respective licenses.

@lucydodo
Copy link
Member Author

lucydodo commented Jan 1, 2024

Although I understand your sentiment, there is a specific reason for this PR.
In order to have notarized when deploying it on macOS, it is necessary to turn on the 'strict code signing' option.
Unfortunately, this also requires us to sign the libraries included in the app with our certificate, which is why I included and signed sqlean it self. (In addition, 'sqlean' includes extensions for most of the known SQLite extensions) :)

Of course, if the user has superuser privileges,
they can force us to break the code signing for app and any libraries they want and load them.

@justinclift
Copy link
Member

we should be agnostic from the application about what extensions we ... automatically load.

This doesn't really sound right to me, as there aren't competing sides who we'd be picking favourites from.

We're making this GUI for our users, and it'll make things that little bit easier for them if we have the sqlean extension enabled by default.

It's one less thing for people to have to worry about, and if their situation is some kind of edge case then they can disable it anyway.

Does that make sense?

@mgrojo
Copy link
Member

mgrojo commented Jan 1, 2024

we should be agnostic from the application about what extensions we ... automatically load.

This doesn't really sound right to me, as there aren't competing sides who we'd be picking favourites from.

Probably, I didn't explain myself correctly. It's not that we should be neutral, what I mean is that 'sqlean' (or any other extension that we distributed and install) should be loaded as default, but through our usual configuration mechanism. That is, I would prefer that users see, when starting the application after installation, just a list of preloaded extensions that they can just edit as usual, adding new extensions or removing them.

We're making this GUI for our users, and it'll make things that little bit easier for them if we have the sqlean extension enabled by default.

It's one less thing for people to have to worry about, and if their situation is some kind of edge case then they can disable it anyway.

Agreed. My remark was only about how to do that, not about not doing it at all.

But if we don't find another way, then I don't mind if this enters the codebase. It's only that I would prefer a more generic approach.

@mgrojo
Copy link
Member

mgrojo commented Jan 1, 2024

What if we simply do something like this:

diff --git a/src/Settings.cpp b/src/Settings.cpp
index bb1e31c4..b36c4187 100644
--- a/src/Settings.cpp
+++ b/src/Settings.cpp
@@ -403,8 +403,13 @@ QVariant Settings::getDefaultValue(const std::string& group, const std::string&
         return true;
 
     // extensions/list?
-    if(group == "extensions" && name == "list")
+    if(group == "extensions" && name == "list") {
+#ifdef Q_OS_MACX
+        return QStringList(qApp->applicationDirPath() + "/../Extensions/sqlean.dylib");
+#else
         return QStringList();
+#endif
+    }
 
     // extensions/disableregex?
     if(group == "extension" && name == "disableregex")

@lucydodo
Copy link
Member Author

lucydodo commented Jan 2, 2024

Got it. So, if I understand correctly, instead of offering only one option like this PR,
we should provide a list of a default extensions and allow the user to choose from them?

I draw a simple prototype to make sure I understood it well :)
PreferencesDialog

@mgrojo
Copy link
Member

mgrojo commented Jan 2, 2024

I was thinking of just treating them as any other extension that the user has loaded, but done automatically by default (like in the patch I suggested). But your approach is also good, and has the advantage of not losing the reference to the file if the user unselects some built-in extension.

@lucydodo
Copy link
Member Author

lucydodo commented Jan 3, 2024

In app bundles on macOS, built-in extensions are stored and distributed in the
DB Browser for SQLite.app/Contents/Extensions,
so you mean it would be better to automatically list-up the files in this path to the PreferencesDialog?

@lucydodo
Copy link
Member Author

lucydodo commented Jan 3, 2024

For now, this PR requires additional code changes, so I'll move it to draft.

@lucydodo lucydodo marked this pull request as draft January 3, 2024 03:57
@lucydodo lucydodo marked this pull request as ready for review January 28, 2024 00:23
@lucydodo
Copy link
Member Author

I've modified the code based on the review, and I'm ready for a new review now.

Now, get a list of extensions that are included in the app bundle, and use checkboxes to let the user decide whether or not to laod them by checking or unchecking them. (Implemented as shown in the screenshot above) Thank you. :)

@lucydodo lucydodo changed the title Add option to auto-load 'sqlean' extension Add option to auto-load built-in extension Jan 28, 2024
@justinclift
Copy link
Member

Awesome. I can't do a proper technical review of this personally. But I did notice some small potential items that seemed a bit strange, so made comments on those.

The potential change of the currentIndex value though is probably the only one that's actually important. 😄

void PreferencesDialog::listUpBuiltinExtension()
{
const QDir dir(qApp->applicationDirPath() + "/../Extensions/");
QStringList files = dir.entryList(QStringList() << "*.dylib", QDir::Files);
Copy link
Member

Choose a reason for hiding this comment

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

I think this is the only part preventing this new feature to be portable. The "*.dylib" could be made OS-dependent and all the #ifdef Q_OS_MACX removed.

Copy link
Member

Choose a reason for hiding this comment

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

Aren't we starting with doing these on macOS first, and then expanding out to other OS's if it goes well?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I was actually going to implement the feature for Windows after I finished this PR, but i's okay to do it in this PR. However, I'm not sure if Linux has a place in the executable file to store the extension library.

Copy link
Member

@mgrojo mgrojo Jan 29, 2024

Choose a reason for hiding this comment

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

At least on Debian/Ubuntu, if you install an SQLite extension, it will be placed under /usr/lib/x86_64-linux-gnu/mod_*.so. Thus, it would make sense to use the path qApp->applicationDirPath() + "/../" + QSysInfo::currentCpuArchitecture + "-linux-gnu/mod-*.so". With that, when the user installs the official Debian sqlitebrowser package and any SQLite extension package, the extension will be listed as an available extension to load. In that way, we will save the user from having to search the extension after the installation.

If this defers per Linux distribution, we might choose to support only one, or try to detect the distribution using QSysInfo::productType().

No problem if this is done for macOS only because it is urgent for the release, and then generalized for other OS's. But in general, we should prefer to make everything as common and portable as possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you so much. This will go a long way toward to resolving the issue.
Let's take a look at a few popular Linux distributions and see where other distributions store their extensions.

As an aside, I personally think it's better not to include this PR in this release. 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I don't know much about Linux-side distributions, so I'll wait for others to chime in. 😄

Copy link
Member

@mgrojo mgrojo Feb 2, 2024

Choose a reason for hiding this comment

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

You're missing the prefix used by SQLite extensions (mod_*.so). On my system:

$ ls /usr/lib/x86_64-linux-gnu/mod_*.so
/usr/lib/x86_64-linux-gnu/mod_spatialite.so
/usr/lib/x86_64-linux-gnu/mod_virtualpg.so
$ ls /usr/lib/x86_64-linux-gnu/*.so | wc -l
261

Copy link
Member

Choose a reason for hiding this comment

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

Oops, just saw that libsqlite3-mod-csvtable uses a different convention: /usr/lib/x86_64-linux-gnu/libsqlite3_mod_csvtable.so. So not so easy, but maybe just a matter of using *mod_*.so, but might be fragile.

Copy link
Member

Choose a reason for hiding this comment

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

Checking list of packages on Debian, most of them are libsqlite3_mod_*.so, but there are a few weird cases, like https://packages.debian.org/sid/amd64/libsqlite3-mod-ceph/filelist

Copy link
Member Author

@lucydodo lucydodo Feb 3, 2024

Choose a reason for hiding this comment

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

I didn't realize I needed to add that prefix. Thanks for letting me know.

But then I thoguht, this is a PR that implements auto-loading for the built-extensions that our application provides, so maybe we don't need to automatially load SQLite extensions installed by external packages on the user's system? (If user need it, user can still add that extension in the Preferences today)

Alternatively, I think we could implement an additional feature to find and automatically load SQLite extensions that users have installed on their system. (Maybe feature name will be 'Load System-wide Installed SQLite Extensions). 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants