changeset 795:14ca14e0fd1e

fixes for sqlite
author Alain Mazy <am@orthanc.team>
date Thu, 22 Jan 2026 07:01:09 +0100
parents b3ee20f1bb83
children 0f0c9103fea8
files Framework/Plugins/ISqlLookupFormatter.cpp Framework/Plugins/ISqlLookupFormatter.h Framework/Plugins/IndexBackend.cpp Framework/SQLite/SQLiteDatabase.cpp Framework/SQLite/SQLiteDatabase.h Resources/CMake/DatabasesFrameworkConfiguration.cmake SQLite/Plugins/IndexPlugin.cpp
diffstat 7 files changed, 143 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Plugins/ISqlLookupFormatter.cpp	Tue Jan 20 16:41:54 2026 +0100
+++ b/Framework/Plugins/ISqlLookupFormatter.cpp	Thu Jan 22 07:01:09 2026 +0100
@@ -135,7 +135,7 @@
         }
         else
         {
-          comparison = "lower(" + tag + ".value) " + op + " lower(" + parameter + ")";
+          comparison = formatter.FormatLower(tag + ".value") + op + formatter.FormatLower(parameter);
         }
 
         break;
@@ -158,7 +158,7 @@
           }
           else
           {
-            comparison += "lower(" + parameter + ")";
+            comparison += formatter.FormatLower(parameter);
           }
         }
 
@@ -168,7 +168,7 @@
         }
         else
         {
-          comparison = "lower(" +  tag + ".value) IN (" + comparison + ")";
+          comparison = formatter.FormatLower(tag + ".value") + " IN (" + comparison + ")";
         }
 
         break;
@@ -188,45 +188,7 @@
         }
         else
         {
-          std::string escaped;
-          escaped.reserve(value.size());
-
-          for (size_t i = 0; i < value.size(); i++)
-          {
-            if (value[i] == '*')
-            {
-              escaped += "%";
-            }
-            else if (value[i] == '?')
-            {
-              escaped += "_";
-            }
-            else if (value[i] == '%')
-            {
-              escaped += "\\%";
-            }
-            else if (value[i] == '_')
-            {
-              escaped += "\\_";
-            }
-            else if (value[i] == '\\')
-            {
-              escaped += "\\\\";
-            }
-            else if (escapeBrackets && value[i] == '[')
-            {
-              escaped += "\\[";
-            }
-            else if (escapeBrackets && value[i] == ']')
-            {
-              escaped += "\\]";
-            }
-            else
-            {
-              escaped += value[i];
-            }
-          }
-
+          std::string escaped = formatter.FormatWildcardsForLike(value);
           std::string parameter = formatter.GenerateParameter(escaped);
           comparison = formatter.FormatLike(isCaseSensitive,
                                             tag + ".value",
@@ -545,7 +507,7 @@
         }
         else
         {
-          comparison = " AND lower(value) " + op + " lower(" + parameter + ")";
+          comparison = " AND " + formatter.FormatLower("value") + op + formatter.FormatLower(parameter);
         }
 
         break;
@@ -564,7 +526,7 @@
           }
           else
           {
-            comparisonValues.push_back("lower(" + parameter + ")");
+            comparisonValues.push_back(formatter.FormatLower(parameter));
           }
         }
 
@@ -576,7 +538,7 @@
         }
         else
         {
-          comparison = " AND lower(value) IN (" + values + ")";
+          comparison = " AND " + formatter.FormatLower("value") + " IN (" + values + ")";
         }
 
         break;
@@ -596,45 +558,7 @@
         }
         else
         {
-          std::string escaped;
-          escaped.reserve(value.size());
-
-          for (size_t i = 0; i < value.size(); i++)
-          {
-            if (value[i] == '*')
-            {
-              escaped += "%";
-            }
-            else if (value[i] == '?')
-            {
-              escaped += "_";
-            }
-            else if (value[i] == '%')
-            {
-              escaped += "\\%";
-            }
-            else if (value[i] == '_')
-            {
-              escaped += "\\_";
-            }
-            else if (value[i] == '\\')
-            {
-              escaped += "\\\\";
-            }
-            else if (escapeBrackets && value[i] == '[')
-            {
-              escaped += "\\[";
-            }
-            else if (escapeBrackets && value[i] == ']')
-            {
-              escaped += "\\]";
-            }
-            else
-            {
-              escaped += value[i];
-            }
-          }
-
+          std::string escaped = formatter.FormatWildcardsForLike(value);
           std::string parameter = formatter.GenerateParameter(escaped);
           comparison = " AND " + formatter.FormatLike(constraint.IsCaseSensitive(),
                                                       "value",
--- a/Framework/Plugins/ISqlLookupFormatter.h	Tue Jan 20 16:41:54 2026 +0100
+++ b/Framework/Plugins/ISqlLookupFormatter.h	Thu Jan 22 07:01:09 2026 +0100
@@ -69,6 +69,10 @@
 
     virtual std::string FormatLike(bool isCaseSensitive, const std::string& a, const std::string& b) = 0;
 
+    virtual std::string FormatWildcardsForLike(const std::string& value) = 0;
+
+    virtual std::string FormatLower(const std::string& value) = 0;
+
     /**
      * Whether to escape '[' and ']', which is only needed for
      * MSSQL. New in Orthanc 1.10.0, from the following changeset:
--- a/Framework/Plugins/IndexBackend.cpp	Tue Jan 20 16:41:54 2026 +0100
+++ b/Framework/Plugins/IndexBackend.cpp	Thu Jan 22 07:01:09 2026 +0100
@@ -48,10 +48,15 @@
   }
 
 
-  static std::string ConvertWildcardToLike(const std::string& query)
+  static std::string ConvertWildcardToLike(const std::string& query, Dialect dialect)
   {
     std::string s = query;
 
+    if (dialect == Dialect_SQLite)
+    {
+      return s; // we are actually using GLOB that keeps the 'Unix' like wildcards
+    }
+
     for (size_t i = 0; i < s.size(); i++)
     {
       if (s[i] == '*')
@@ -1432,7 +1437,7 @@
 
     if (constraint == OrthancPluginIdentifierConstraint_Wildcard)
     {
-      args.SetUtf8Value("value", ConvertWildcardToLike(value));
+      args.SetUtf8Value("value", ConvertWildcardToLike(value, manager.GetDialect()));
     }
     else
     {
@@ -2226,6 +2231,72 @@
       }
     }
 
+    virtual std::string FormatLower(const std::string& value)
+    {
+      switch (dialect_)
+      {
+        case Dialect_SQLite:
+        {
+          return " lower_with_accents(" + value + ") ";
+        };
+        default:
+          return " lower(" + value + ") ";
+      }
+    }
+
+    virtual std::string FormatWildcardsForLike(const std::string& value)
+    {
+      bool escapeBrackets = IsEscapeBrackets();
+      std::string escaped;
+      escaped.reserve(value.size());
+
+      switch (dialect_)
+      {
+        case Dialect_SQLite:
+        {
+          escaped = value;  // SQLite uses GLOB instead of LIKE -> no need for escaping and we keep the 'Unix' like wildcards
+        }; break;
+        default:
+          for (size_t i = 0; i < value.size(); i++)
+          {
+            if (value[i] == '*')
+            {
+              escaped += "%";
+            }
+            else if (value[i] == '?')
+            {
+              escaped += "_";
+            }
+            else if (value[i] == '%')
+            {
+              escaped += "\\%";
+            }
+            else if (value[i] == '_')
+            {
+              escaped += "\\_";
+            }
+            else if (value[i] == '\\')
+            {
+              escaped += "\\\\";
+            }
+            else if (escapeBrackets && value[i] == '[')
+            {
+              escaped += "\\[";
+            }
+            else if (escapeBrackets && value[i] == ']')
+            {
+              escaped += "\\]";
+            }
+            else
+            {
+              escaped += value[i];
+            }
+          }
+      }
+      return escaped;
+    }
+
+
     virtual std::string FormatLike(bool isCaseSensitive, const std::string& a, const std::string& b)
     {
       switch (dialect_)
@@ -2241,6 +2312,7 @@
             return a + " LIKE " + b + " " + FormatWildcardEscape();
           }
         }; break;
+        case Dialect_MSSQL:
         case Dialect_PostgreSQL: // LIKE is case sensitive by default !
         {
           if (isCaseSensitive)
@@ -2252,8 +2324,17 @@
             return "lower(" + a + ") LIKE lower(" + b + ") " + FormatWildcardEscape();
           }
         }; break;
-        case Dialect_MSSQL:
         case Dialect_SQLite:
+        {
+          if (isCaseSensitive)
+          {
+            return a + " GLOB " + b + " "; // + FormatWildcardEscape();
+          }
+          else
+          {
+            return "lower_with_accents(" + a + ") GLOB lower_with_accents(" + b + ") "; // + FormatWildcardEscape();
+          }
+        }; break;
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
       }
--- a/Framework/SQLite/SQLiteDatabase.cpp	Tue Jan 20 16:41:54 2026 +0100
+++ b/Framework/SQLite/SQLiteDatabase.cpp	Thu Jan 22 07:01:09 2026 +0100
@@ -28,6 +28,7 @@
 #include "../Common/ImplicitTransaction.h"
 
 #include <OrthancException.h>
+#include <Toolbox.h>
 
 namespace OrthancDatabases
 {
@@ -114,4 +115,44 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
   }
+
+  class LowerWithAccents : public Orthanc::SQLite::IScalarFunction
+  {
+  public:
+    LowerWithAccents()
+    {
+    }
+
+    virtual const char* GetName() const ORTHANC_OVERRIDE
+    {
+      return "lower_with_accents";
+    }
+
+    virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE
+    {
+      return 1;
+    }
+
+    virtual void Compute(Orthanc::SQLite::FunctionContext& context) ORTHANC_OVERRIDE
+    {
+      std::string source = context.GetStringValue(0);
+      std::string modified = Orthanc::Toolbox::ToLowerCaseWithAccents(source);
+
+      context.SetStringResult(modified);
+    }
+
+  };
+
+  void SQLiteDatabase::OpenInMemory()
+  {
+    connection_.OpenInMemory();
+    connection_.Register(new LowerWithAccents());
+  }
+
+  void SQLiteDatabase::Open(const std::string& path)
+  {
+    connection_.Open(path);
+    connection_.Register(new LowerWithAccents());
+  }
+
 }
--- a/Framework/SQLite/SQLiteDatabase.h	Tue Jan 20 16:41:54 2026 +0100
+++ b/Framework/SQLite/SQLiteDatabase.h	Thu Jan 22 07:01:09 2026 +0100
@@ -39,15 +39,9 @@
     Orthanc::SQLite::Connection  connection_;
     
   public:
-    void OpenInMemory()
-    {
-      connection_.OpenInMemory();
-    }
+    void OpenInMemory();
 
-    void Open(const std::string& path)
-    {
-      connection_.Open(path);
-    }
+    void Open(const std::string& path);
     
     Orthanc::SQLite::Connection& GetObject()
     {
--- a/Resources/CMake/DatabasesFrameworkConfiguration.cmake	Tue Jan 20 16:41:54 2026 +0100
+++ b/Resources/CMake/DatabasesFrameworkConfiguration.cmake	Thu Jan 22 07:01:09 2026 +0100
@@ -26,6 +26,7 @@
 
 if (ENABLE_SQLITE_BACKEND)
   set(ENABLE_SQLITE ON)
+  set(ENABLE_LOCALE ON)      # iconv is needed for lower_with_accents
 endif()
 
 if (ENABLE_POSTGRESQL_BACKEND)
--- a/SQLite/Plugins/IndexPlugin.cpp	Tue Jan 20 16:41:54 2026 +0100
+++ b/SQLite/Plugins/IndexPlugin.cpp	Thu Jan 22 07:01:09 2026 +0100
@@ -25,6 +25,7 @@
 #include "../../Framework/Plugins/PluginInitialization.h"
 
 #include <Logging.h>
+#include <Toolbox.h>
 
 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0)
 #  include <google/protobuf/any.h>
@@ -42,6 +43,8 @@
     GOOGLE_PROTOBUF_VERIFY_VERSION;
 #endif
 
+    Orthanc::Toolbox::InitializeGlobalLocale(NULL);
+
     if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "SQLite", true))
     {
       return -1;