Skip to content

Fix single/multi select DateTime filter#4495

Open
gabriel-bolbotina wants to merge 5 commits into
masterfrom
fix-filtering-dateTime-singleMulti-select
Open

Fix single/multi select DateTime filter#4495
gabriel-bolbotina wants to merge 5 commits into
masterfrom
fix-filtering-dateTime-singleMulti-select

Conversation

@gabriel-bolbotina

Copy link
Copy Markdown
Contributor

GeoPackage can store DateTime as UTC with or without milliseconds.
The original code assumed that the DateTime entry for Single/Multi select contained milliseconds, which is not always the case if a point is added in QGIS compared to one saved with the mobile app.

Fix: check if the value contains milliseconds and change the format when creating the filter

@github-actions

github-actions Bot commented May 14, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 27557989684

Warning

No base build found for commit 93fb13e on master.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 59.068%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 15257
Covered Lines: 9012
Line Coverage: 59.07%
Coverage Strength: 99.01 hits per line

💛 - Coveralls

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts Ready

OS Status Build Info Workflow run
macOS Build 📬 Mergin Maps 68441 dmg Expires: 12/08/2026 #6844
linux Build 📬 Mergin Maps 68621 x86_64 Expires: 12/08/2026 #6862
win64 Build 📬 Mergin Maps 60371 win64 Expires: 12/08/2026 #6037
Android Build 📬 Mergin Maps 815451 APK [arm64-v8a] Expires: 12/08/2026 #8154
📬 Mergin Maps 815451 APK [arm64-v8a] Google Play Store #8154
Android Build 📬 Mergin Maps 815411 APK [armeabi-v7a] Expires: 12/08/2026 #8154
📬 Mergin Maps 815411 APK [armeabi-v7a] Google Play Store #8154
iOS Build Build failed or not found. #9096

Comment thread app/filter/filtercontroller.cpp Outdated
@Withalion

Copy link
Copy Markdown
Contributor

I have a bad feeling though that, if the user will use now() expression as default value, which saves miliseconds too and manages to get 0 miliseconds. The filter won't be able to filter that value, so we should either try both values or get the string value before it's transformed to QDateTime/ JS Date.

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts Ready

OS Status Build Info Workflow run
macOS Build 📬 Mergin Maps 68551 dmg Expires: 17/08/2026 #6855
linux Build 📬 Mergin Maps 68731 x86_64 Expires: 17/08/2026 #6873
win64 Build 📬 Mergin Maps 60491 win64 Expires: 17/08/2026 #6049
Android Build 📬 Mergin Maps 816511 APK [armeabi-v7a] Expires: 17/08/2026 #8165
📬 Mergin Maps 816511 APK [armeabi-v7a] Google Play Store #8165
Android Build 📬 Mergin Maps 816551 APK [arm64-v8a] Expires: 17/08/2026 #8165
📬 Mergin Maps 816551 APK [arm64-v8a] Google Play Store #8165
iOS Build Build failed or not found. #9107

@gabriel-bolbotina gabriel-bolbotina force-pushed the fix-filtering-dateTime-singleMulti-select branch from bed2d9c to 429d49c Compare June 2, 2026 13:27
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

📦 Build Artifacts Ready

OS Status Build Info Workflow run
macOS Build 📬 Mergin Maps 68791 dmg Expires: 31/08/2026 #6879
linux Build 📬 Mergin Maps 69051 x86_64 Expires: 31/08/2026 #6905
win64 Build 📬 Mergin Maps 60731 win64 Expires: 31/08/2026 #6073
Android Build 📬 Mergin Maps 818911 APK [armeabi-v7a] Expires: 31/08/2026 #8189
📬 Mergin Maps 818911 APK [armeabi-v7a] Google Play Store #8189
Android Build 📬 Mergin Maps 818951 APK [arm64-v8a] Expires: 31/08/2026 #8189
📬 Mergin Maps 818951 APK [arm64-v8a] Google Play Store #8189
iOS Build 📬 Build number: 26.06.913111 #9131

@Withalion Withalion requested a review from tomasMizera June 5, 2026 10:19

@tomasMizera tomasMizera left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we add unit tests here?

Added helper methods to  testUtils
Changed single/ multi select filter to support IS NULL
@github-actions

Copy link
Copy Markdown

📦 Build Artifacts Ready

OS Status Build Info Workflow run
macOS Build Build failed or not found. #6927
linux Build 📬 Mergin Maps 69531 x86_64 Expires: 13/09/2026 #6953
win64 Build Build failed or not found. #6125
Android Build 📬 Mergin Maps 823751 APK [arm64-v8a] Expires: 13/09/2026 #8237
📬 Mergin Maps 823751 APK [arm64-v8a] Google Play Store #8237
Android Build 📬 Mergin Maps 823711 APK [armeabi-v7a] Expires: 13/09/2026 #8237
📬 Mergin Maps 823711 APK [armeabi-v7a] Google Play Store #8237
iOS Build 📬 Build number: 26.06.917911 #9179

@Withalion Withalion left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The tests look okay in general, let's just improve the approach.

QStringList expressions;
QString expressionTemplate( expressionCopy );
expressionTemplate.replace( QStringLiteral( "@@value@@" ), QStringLiteral( "NULL" ) );
expressionTemplate.replace( QStringLiteral( "= NULL" ), QStringLiteral( "IS NULL" ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if there was no change in plugin then the sql query should be column IS value. Did something change in the meantime?

if ( QgsVariantUtils::isNull( value ) )
{
expressionTemplate.replace( QStringLiteral( "@@value@@" ), QStringLiteral( "NULL" ) );
expressionTemplate.replace( QStringLiteral( "= NULL" ), QStringLiteral( "IS NULL" ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as above

Comment on lines +27 to +47
void testDateRange_dateTime();
void testDateRange_date();
void testDateRange_dateTime_null(); // invalid bounds fall back to min/max sentinels
void testDateRange_date_null(); // invalid bounds fall back to min/max sentinels
void testDateRange_dateTime_featureAtLowerBound(); // >= is inclusive: feature exactly at from-bound is counted
void testDateRange_dateTime_midnightLowerBound(); // from=midnight; midnight feature is at the inclusive lower bound
void testDateRange_dateTime_zeroMsInsideRange(); // 0 ms feature inside range: range uses >= / <=, no double-expr needed

// Single select
void testSingleSelect_dateTime_nonZeroMs();
void testSingleSelect_dateTime_zeroMs(); // edge case: 0 ms
void testSingleSelect_dateTime_null(); // null -> NULL OR ''
void testSingleSelect_date();

// Multi select
void testMultiSelect_dateTime_nonZeroMs();
void testMultiSelect_dateTime_zeroMs(); // edge case: 0 ms
void testMultiSelect_dateTime_mixed(); // mix of 0 ms and non-zero ms values
void testMultiSelect_dateTime_null(); // null -> NULL OR ''
void testMultiSelect_dateTime_empty(); // empty list -> no subset string
void testMultiSelect_date();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

use camelCase or PascalCase, which is used in the rest of repo

Comment thread app/test/testutils.h
Comment on lines +70 to +85

//! Creates an in-memory layer with a single field of the given type and registers it in QgsProject::instance()
//! fieldType is the QGIS memory-provider type string: "datetime", "date", "string", "integer", "double", "bool", etc.
QgsVectorLayer *createFilterTestLayer( const QString &fieldName,
const QString &fieldType,
const QString &layerName = QStringLiteral( "FilterTestLayer" ) );

//! Appends a single feature to layer via the data provider; value is stored in the named field. Returns false if addFeatures() fails.
bool addFeatureToLayer( QgsVectorLayer *layer, const QString &fieldName, const QVariant &value );

//! Writes a single-filter config into the project and loads it; returns the assigned filterId
QString setupControllerWithFilter( FilterController *controller,
FieldFilter::FilterType filterType,
const QString &layerId,
const QString &fieldName,
const QString &sqlExpression );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Instead of doing this in the code create a QGIS project, which will have filters, layers, features already setup. That should also mitigate the filter definition duplication in testfiltercontroller.cpp. Place the project in test/test_data/ and load it before running the tests

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Filters - Single Select and Multi Select do not work correctly with DateTime fields

3 participants