diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index 92cf234cb315..e09064ce1497 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -9739,3 +9739,54 @@ def test_ogr_gpkg_arrow_stream_huge_array(tmp_vsimem, too_big_field): assert got_fids == [i + 1 for i in range(50)] assert batch_count == (25 if too_big_field == "geometry" else 21), lyr_name del stream + + +############################################################################### +# Test our overloaded LIKE operator + + +@gdaltest.enable_exceptions() +def test_ogr_gpkg_like_utf8(tmp_vsimem): + + filename = str(tmp_vsimem / "test_ogr_gpkg_like_utf8.gpkg") + ds = ogr.GetDriverByName("GPKG").CreateDataSource(filename) + lyr = ds.CreateLayer("test") + lyr.CreateFeature(ogr.Feature(lyr.GetLayerDefn())) + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'i'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'é' LIKE 'É'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'éx' LIKE 'Éxx' ESCAPE 'x'" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL("SELECT * FROM test WHERE NULL LIKE 'É'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'é' LIKE NULL") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'é' LIKE 'É' ESCAPE NULL") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'é' LIKE 'É' ESCAPE 'should be single char'" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + ds.ExecuteSQL("PRAGMA case_sensitive_like = 1") + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + ds.ExecuteSQL("PRAGMA case_sensitive_like = 0") + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 diff --git a/autotest/ogr/ogr_sql_sqlite.py b/autotest/ogr/ogr_sql_sqlite.py index e67149a2d43b..fd7b645c7f42 100755 --- a/autotest/ogr/ogr_sql_sqlite.py +++ b/autotest/ogr/ogr_sql_sqlite.py @@ -2426,3 +2426,56 @@ def test_ogr_sql_sqlite_unsupported(sql): lyr.CreateField(ogr.FieldDefn("foo")) with pytest.raises(Exception): ds.ExecuteSQL(sql, dialect="SQLite") + + +############################################################################### +# Test our overloaded LIKE operator + + +@gdaltest.enable_exceptions() +def test_ogr_sql_sqlite_like_utf8(): + + ds = ogr.GetDriverByName("Memory").CreateDataSource("") + lyr = ds.CreateLayer("test", options=["ADVERTIZE_UTF8=YES"]) + lyr.CreateFeature(ogr.Feature(lyr.GetLayerDefn())) + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'e' LIKE 'E'", dialect="SQLite" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'e' LIKE 'i'", dialect="SQLite" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'é' LIKE 'É'", dialect="SQLite" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'éx' LIKE 'Éxx' ESCAPE 'x'", dialect="SQLite" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE NULL LIKE 'É'", dialect="SQLite" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'é' LIKE NULL", dialect="SQLite" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'é' LIKE 'É' ESCAPE NULL", dialect="SQLite" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'é' LIKE 'É' ESCAPE 'should be single char'", + dialect="SQLite", + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 diff --git a/autotest/ogr/ogr_sqlite.py b/autotest/ogr/ogr_sqlite.py index f6c285e1605b..35b9800b97ef 100755 --- a/autotest/ogr/ogr_sqlite.py +++ b/autotest/ogr/ogr_sqlite.py @@ -3991,3 +3991,53 @@ def test_ogr_sql_sql_first_geom_null(require_spatialite): assert sql_lyr.GetGeometryColumn() == "ST_Buffer(geom,0.1)" with ds.ExecuteSQL("SELECT ST_Buffer(geom,0.1), * FROM test") as sql_lyr: assert sql_lyr.GetGeometryColumn() == "ST_Buffer(geom,0.1)" + + +############################################################################### +# Test our overloaded LIKE operator + + +@gdaltest.enable_exceptions() +def test_ogr_sqlite_like_utf8(): + + ds = ogr.GetDriverByName("SQLite").CreateDataSource(":memory:") + lyr = ds.CreateLayer("test") + lyr.CreateFeature(ogr.Feature(lyr.GetLayerDefn())) + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'i'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'é' LIKE 'É'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'éx' LIKE 'Éxx' ESCAPE 'x'" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL("SELECT * FROM test WHERE NULL LIKE 'É'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'é' LIKE NULL") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'é' LIKE 'É' ESCAPE NULL") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( + "SELECT * FROM test WHERE 'é' LIKE 'É' ESCAPE 'should be single char'" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + ds.ExecuteSQL("PRAGMA case_sensitive_like = 1") + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + ds.ExecuteSQL("PRAGMA case_sensitive_like = 0") + + with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 diff --git a/doc/source/user/sql_sqlite_dialect.rst b/doc/source/user/sql_sqlite_dialect.rst index f46291994b28..c1e943746c29 100644 --- a/doc/source/user/sql_sqlite_dialect.rst +++ b/doc/source/user/sql_sqlite_dialect.rst @@ -80,6 +80,22 @@ translated, as far as possible, as attribute filters that are applied on the underlying OGR layers. Joins can be very expensive operations if the secondary table is not indexed on the key field being used. +LIKE operator ++++++++++++++ + +In SQLite, the LIKE operator is case insensitive, unless ``PRAGMA case_sensitive_like = 1`` +has been issued. + +Starting with GDAL 3.9, GDAL installs a custom LIKE comparison, such that UTF-8 +characters are taken into account by ``LIKE`` and ``ILIKE`` operators. +For ILIKE case insensitive comparisons, this is restricted to the +`ASCII `__, +`Latin-1 Supplement `__, +`Latin Extended-A `__, +`Latin Extended-B `__, +`Greek and Coptic `__ +and `Cyrillic `__ Unicode categories. + Delimited identifiers +++++++++++++++++++++ diff --git a/ogr/ogr_swq.h b/ogr/ogr_swq.h index 81ee1ff1e770..5e3ccf34862a 100644 --- a/ogr/ogr_swq.h +++ b/ogr/ogr_swq.h @@ -483,6 +483,8 @@ char CPL_UNSTABLE_API *OGRHStoreGetValue(const char *pszHStore, void swq_fixup(swq_parse_context *psParseContext); swq_expr_node *swq_create_and_or_or(swq_op op, swq_expr_node *left, swq_expr_node *right); +int swq_test_like(const char *input, const char *pattern, char chEscape, + bool insensitive, bool bUTF8Strings); #endif #endif /* #ifndef DOXYGEN_SKIP */ diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp index 0a9c9f3ea6fa..43fbac17ab4b 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp @@ -46,6 +46,7 @@ #include #define COMPILATION_ALLOWED +#define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike #include "ogrsqlitesqlfunctionscommon.cpp" // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp, @@ -7146,6 +7147,21 @@ OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand, } } + if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0")) + { + OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false); + } + else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1")) + { + OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true); + } + /* -------------------------------------------------------------------- */ /* DEBUG "SELECT nolock" command. */ /* -------------------------------------------------------------------- */ @@ -8910,7 +8926,9 @@ static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/, #define SQLITE_INNOCUOUS 0 #endif +#ifndef UTF8_INNOCUOUS #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS) +#endif void GDALGeoPackageDataset::InstallSQLFunctions() { diff --git a/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h b/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h index a075488164eb..f933afb954e4 100644 --- a/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h +++ b/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h @@ -632,6 +632,8 @@ class OGRSQLiteSelectLayer CPL_NON_FINAL : public OGRSQLiteLayer, /* OGRSQLiteDataSource */ /************************************************************************/ +class OGR2SQLITEModule; + class OGRSQLiteDataSource final : public OGRSQLiteBaseDataSource { OGRSQLiteLayer **m_papoLayers = nullptr; @@ -688,6 +690,8 @@ class OGRSQLiteDataSource final : public OGRSQLiteBaseDataSource OGRSQLiteDataSource *m_poParentDS = nullptr; std::vector m_apoOverviewDS{}; + OGR2SQLITEModule *m_poSQLiteModule = nullptr; + #ifdef HAVE_RASTERLITE2 void ListOverviews(); void CreateRL2OverviewDatasetIfNeeded(double dfXRes, double dfYRes); diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp b/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp index e845a9b6b8e5..8462f141e8bd 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp +++ b/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp @@ -1673,7 +1673,7 @@ bool OGRSQLiteDataSource::OpenOrCreateDB(int flagsIn, // that will do it with other datasets. CPLTestBool(CPLGetConfigOption("OGR_SQLITE_STATIC_VIRTUAL_OGR", "YES"))) { - OGR2SQLITE_Setup(this, this); + m_poSQLiteModule = OGR2SQLITE_Setup(this, this); } // We need to do LoadExtensions() after OGR2SQLITE_Setup(), otherwise // tests in ogr_virtualogr.py::test_ogr_sqlite_load_extensions_load_self() @@ -3213,6 +3213,23 @@ OGRLayer *OGRSQLiteDataSource::ExecuteSQL(const char *pszSQLCommand, return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter, "SQLITE"); + if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0")) + { + if (m_poSQLiteModule) + OGR2SQLITE_SetCaseSensitiveLike(m_poSQLiteModule, false); + } + else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") || + EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1")) + { + if (m_poSQLiteModule) + OGR2SQLITE_SetCaseSensitiveLike(m_poSQLiteModule, true); + } + /* -------------------------------------------------------------------- */ /* Special case DELLAYER: command. */ /* -------------------------------------------------------------------- */ diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctions.cpp b/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctions.cpp index 25572dd2d0ac..2bcee8074a13 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctions.cpp +++ b/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctions.cpp @@ -1010,7 +1010,9 @@ static void OGRSQLITE_hstore_get_value(sqlite3_context *pContext, #define SQLITE_INNOCUOUS 0 #endif +#ifndef UTF8_INNOCUOUS #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS) +#endif static void *OGRSQLiteRegisterSQLFunctions(sqlite3 *hDB) { diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp b/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp index 02af234909a3..d6752d74dc07 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp +++ b/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp @@ -39,6 +39,8 @@ #include +#include "ogr_swq.h" + namespace { class OGRSQLiteExtensionData @@ -54,6 +56,8 @@ class OGRSQLiteExtensionData OGRGeocodingSessionH hGeocodingSession = nullptr; + bool bCaseSensitiveLike = false; + OGRSQLiteExtensionData(const OGRSQLiteExtensionData &) = delete; OGRSQLiteExtensionData &operator=(const OGRSQLiteExtensionData &) = delete; @@ -80,6 +84,16 @@ class OGRSQLiteExtensionData { hRegExpCache = hRegExpCacheIn; } + + void SetCaseSensitiveLike(bool b) + { + bCaseSensitiveLike = b; + } + + bool GetCaseSensitiveLike() const + { + return bCaseSensitiveLike; + } }; /************************************************************************/ @@ -279,6 +293,46 @@ static void OGRSQLITE_gdal_get_pixel_value(sqlite3_context *pContext, } } +/************************************************************************/ +/* OGRSQLITE_LIKE() */ +/************************************************************************/ + +static void OGRSQLITE_LIKE(sqlite3_context *pContext, int argc, + sqlite3_value **argv) +{ + OGRSQLiteExtensionData *poModule = + static_cast(sqlite3_user_data(pContext)); + + // A LIKE B is implemented as like(B, A) + // A LIKE B ESCAPE C is implemented as like(B, A, C) + const char *pattern = + reinterpret_cast(sqlite3_value_text(argv[0])); + const char *input = + reinterpret_cast(sqlite3_value_text(argv[1])); + if (!input || !pattern) + { + sqlite3_result_null(pContext); + return; + } + char chEscape = '\\'; + if (argc == 3) + { + const char *escape = + reinterpret_cast(sqlite3_value_text(argv[2])); + if (!escape || escape[1] != 0) + { + sqlite3_result_null(pContext); + return; + } + chEscape = escape[0]; + } + + const bool insensitive = !poModule->GetCaseSensitiveLike(); + constexpr bool bUTF8Strings = true; + sqlite3_result_int(pContext, swq_test_like(input, pattern, chEscape, + insensitive, bUTF8Strings)); +} + /************************************************************************/ /* OGRSQLiteRegisterSQLFunctionsCommon() */ /************************************************************************/ @@ -287,6 +341,12 @@ static void OGRSQLITE_gdal_get_pixel_value(sqlite3_context *pContext, #define SQLITE_DETERMINISTIC 0 #endif +#ifndef SQLITE_INNOCUOUS +#define SQLITE_INNOCUOUS 0 +#endif + +#define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS) + static OGRSQLiteExtensionData *OGRSQLiteRegisterSQLFunctionsCommon(sqlite3 *hDB) { OGRSQLiteExtensionData *pData = new OGRSQLiteExtensionData(hDB); @@ -294,6 +354,14 @@ static OGRSQLiteExtensionData *OGRSQLiteRegisterSQLFunctionsCommon(sqlite3 *hDB) sqlite3_create_function(hDB, "gdal_get_pixel_value", 5, SQLITE_UTF8, pData, OGRSQLITE_gdal_get_pixel_value, nullptr, nullptr); + if (CPLTestBool(CPLGetConfigOption("OGR_SQLITE_USE_CUSTOM_LIKE", "YES"))) + { + sqlite3_create_function(hDB, "LIKE", 2, UTF8_INNOCUOUS, pData, + OGRSQLITE_LIKE, nullptr, nullptr); + sqlite3_create_function(hDB, "LIKE", 3, UTF8_INNOCUOUS, pData, + OGRSQLITE_LIKE, nullptr, nullptr); + } + pData->SetRegExpCache(OGRSQLiteRegisterRegExpFunction(hDB)); return pData; @@ -309,3 +377,16 @@ static void OGRSQLiteUnregisterSQLFunctions(void *hHandle) static_cast(hHandle); delete pData; } + +#ifdef DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike +/************************************************************************/ +/* OGRSQLiteSQLFunctionsSetCaseSensitiveLike() */ +/************************************************************************/ + +static void OGRSQLiteSQLFunctionsSetCaseSensitiveLike(void *hHandle, bool b) +{ + OGRSQLiteExtensionData *pData = + static_cast(hHandle); + pData->SetCaseSensitiveLike(b); +} +#endif diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.cpp b/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.cpp index f637360eb663..193ad3e3e4d8 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.cpp +++ b/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.cpp @@ -26,6 +26,8 @@ * DEALINGS IN THE SOFTWARE. ****************************************************************************/ +#define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike + #include "cpl_port.h" #include "ogrsqlitevirtualogr.h" @@ -79,6 +81,10 @@ OGR2SQLITEModule *OGR2SQLITE_Setup(GDALDataset *, OGRSQLiteDataSource *) return nullptr; } +void OGR2SQLITE_SetCaseSensitiveLike(OGR2SQLITEModule *, bool) +{ +} + int OGR2SQLITE_AddExtraDS(OGR2SQLITEModule *, OGRDataSource *) { return 0; @@ -191,6 +197,11 @@ static SQLITE_EXTENSION_INIT1 OGRLayer *GetLayerForVTable(const char *pszVTableName); void SetHandleSQLFunctions(void *hHandleSQLFunctionsIn); + + void SetCaseSensitiveLike(bool b) + { + OGRSQLiteSQLFunctionsSetCaseSensitiveLike(hHandleSQLFunctions, b); + } }; /************************************************************************/ @@ -2721,6 +2732,15 @@ OGR2SQLITEModule *OGR2SQLITE_Setup(GDALDataset *poDS, return poModule; } +/************************************************************************/ +/* OGR2SQLITE_SetCaseSensitiveLike() */ +/************************************************************************/ + +void OGR2SQLITE_SetCaseSensitiveLike(OGR2SQLITEModule *poModule, bool b) +{ + poModule->SetCaseSensitiveLike(b); +} + /************************************************************************/ /* OGR2SQLITE_AddExtraDS() */ /************************************************************************/ diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.h b/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.h index fd14d5105b28..d92f7c5c9b74 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.h +++ b/ogr/ogrsf_frmts/sqlite/ogrsqlitevirtualogr.h @@ -37,6 +37,8 @@ class OGR2SQLITEModule; OGR2SQLITEModule *OGR2SQLITE_Setup(GDALDataset *poDS, OGRSQLiteDataSource *poSQLiteDS); +void OGR2SQLITE_SetCaseSensitiveLike(OGR2SQLITEModule *poModule, bool b); + int OGR2SQLITE_AddExtraDS(OGR2SQLITEModule *poModule, OGRDataSource *poDS); void OGR2SQLITE_Register(); diff --git a/ogr/swq_op_general.cpp b/ogr/swq_op_general.cpp index d6b145bb886b..81bab30b0aca 100644 --- a/ogr/swq_op_general.cpp +++ b/ogr/swq_op_general.cpp @@ -52,8 +52,8 @@ /* Does input match pattern? */ /************************************************************************/ -static int swq_test_like(const char *input, const char *pattern, char chEscape, - bool insensitive, bool bUTF8Strings) +int swq_test_like(const char *input, const char *pattern, char chEscape, + bool insensitive, bool bUTF8Strings) { if (input == nullptr || pattern == nullptr)