|
| 1 | +# Development Journal - November 4, 2025 |
| 2 | + |
| 3 | +## Missing Query Filter Property Types |
| 4 | + |
| 5 | +### 🎯 Objective |
| 6 | +Identify and implement missing property type filters in the DataSourceQueryBuilder. During integration with another project, we discovered that relation properties (a very common use case) are not supported. This investigation aims to catalog all missing types and implement them. |
| 7 | + |
| 8 | +### 🔍 Discovery |
| 9 | + |
| 10 | +#### Investigation Location |
| 11 | +- **Builder Implementation**: `src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQueryBuilder.kt` |
| 12 | +- **Data Models**: `src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQuery.kt` |
| 13 | +- **API Documentation**: `reference/notion-api/documentation/general/07_Filter_Database_Entries.md` |
| 14 | + |
| 15 | +#### Currently Supported Property Types (10 types) |
| 16 | +1. ✅ **title** - text operations (equals, contains, starts_with, ends_with, is_empty, etc.) |
| 17 | +2. ✅ **rich_text** - text operations (same as title) |
| 18 | +3. ✅ **number** - numeric comparisons (equals, greater_than, less_than, etc.) |
| 19 | +4. ✅ **select** - equals, does_not_equal, is_empty, is_not_empty |
| 20 | +5. ✅ **multi_select** - contains, does_not_contain, is_empty, is_not_empty |
| 21 | +6. ✅ **date** - temporal comparisons (equals, before, after, past_week, next_month, etc.) |
| 22 | +7. ✅ **checkbox** - boolean comparisons (equals, does_not_equal) |
| 23 | +8. ✅ **url** - text operations (same as rich_text) |
| 24 | +9. ✅ **email** - text operations (same as rich_text) |
| 25 | +10. ✅ **phone_number** - text operations (same as rich_text) |
| 26 | + |
| 27 | +#### Missing Property Types (8 types) |
| 28 | + |
| 29 | +According to the Notion API documentation (line 85 in the filter docs), the following property types support filtering but are **NOT YET IMPLEMENTED**: |
| 30 | + |
| 31 | +##### High Priority (Common Use Cases) |
| 32 | +1. ❌ **relation** - Filter by related pages |
| 33 | + - Operations: `contains`, `does_not_contain`, `is_empty`, `is_not_empty` |
| 34 | + - Value type: UUID string |
| 35 | + - Use case: Very common for filtering by related entities |
| 36 | + - API docs: lines 286-306 |
| 37 | + |
| 38 | +2. ❌ **people** - Filter by users (also applies to created_by, last_edited_by) |
| 39 | + - Operations: `contains`, `does_not_contain`, `is_empty`, `is_not_empty` |
| 40 | + - Value type: UUID string |
| 41 | + - Use case: Filter by assignee, creator, or editor |
| 42 | + - API docs: lines 260-284 |
| 43 | + |
| 44 | +3. ❌ **status** - Filter by status property |
| 45 | + - Operations: `equals`, `does_not_equal`, `is_empty`, `is_not_empty` |
| 46 | + - Value type: string |
| 47 | + - Use case: Common for workflow/project management databases |
| 48 | + - API docs: lines 429-449 |
| 49 | + |
| 50 | +4. ❌ **unique_id** - Filter by auto-incrementing ID property |
| 51 | + - Operations: `equals`, `does_not_equal`, `greater_than`, `less_than`, `greater_than_or_equal_to`, `less_than_or_equal_to` |
| 52 | + - Value type: number (NOT a UUID, despite the name) |
| 53 | + - Use case: Range queries on sequential IDs |
| 54 | + - API docs: lines 475-509 |
| 55 | + - Note: Uses "unique_id" as the filter key, not "ID" |
| 56 | + |
| 57 | +##### Medium Priority |
| 58 | +5. ❌ **files** - Filter by file attachment presence |
| 59 | + - Operations: `is_empty`, `is_not_empty` |
| 60 | + - Use case: Find pages with/without attachments |
| 61 | + - API docs: lines 166-184 |
| 62 | + |
| 63 | +6. ❌ **timestamp** - Filter by created_time or last_edited_time |
| 64 | + - Special filter type (doesn't use "property" field) |
| 65 | + - Operations: Uses date filter conditions |
| 66 | + - Use case: Find recently created/edited pages |
| 67 | + - API docs: lines 452-473 |
| 68 | + - Note: This is distinct from date properties; applies to page metadata |
| 69 | + |
| 70 | +##### Lower Priority (Complex Types) |
| 71 | +7. ❌ **formula** - Filter by formula results |
| 72 | + - Nested filter structure based on formula return type |
| 73 | + - Sub-filters: `checkbox`, `date`, `number`, `string` (using rich_text conditions) |
| 74 | + - Use case: Filter by computed values |
| 75 | + - API docs: lines 186-210 |
| 76 | + |
| 77 | +8. ❌ **rollup** - Filter by rollup aggregate values |
| 78 | + - Complex nested filter structure |
| 79 | + - For arrays: `any`, `every`, `none` (with nested type filters) |
| 80 | + - For aggregates: `date` or `number` (with corresponding filter conditions) |
| 81 | + - Use case: Filter by aggregated relation data |
| 82 | + - API docs: lines 334-405 |
| 83 | + |
| 84 | +### 📋 Implementation Plan |
| 85 | + |
| 86 | +#### Phase 1: Core Missing Types (High Priority) |
| 87 | +Implement the four most commonly used missing types: |
| 88 | +1. Relation filters |
| 89 | +2. People filters |
| 90 | +3. Status filters |
| 91 | +4. Unique ID filters |
| 92 | + |
| 93 | +These follow the same DSL patterns as existing filters and have straightforward implementations. |
| 94 | + |
| 95 | +#### Phase 2: Simple Utility Type |
| 96 | +5. Files filters (very simple, only is_empty/is_not_empty) |
| 97 | + |
| 98 | +#### Phase 3: Special Cases |
| 99 | +6. Timestamp filters (requires special handling, no property name) |
| 100 | + |
| 101 | +#### Phase 4: Complex Types (if needed) |
| 102 | +7. Formula filters (nested structure) |
| 103 | +8. Rollup filters (most complex, nested structure with multiple modes) |
| 104 | + |
| 105 | +### 🔧 Implementation Notes |
| 106 | + |
| 107 | +#### Pattern Consistency |
| 108 | +All new filter builders should follow the established pattern: |
| 109 | +- FilterBuilder extension method (e.g., `fun relation(propertyName: String)`) |
| 110 | +- Dedicated builder class (e.g., `RelationFilterBuilder`) |
| 111 | +- Condition data class (e.g., `RelationCondition`) |
| 112 | +- DataSourceFilter extension in DataSourceQuery.kt |
| 113 | + |
| 114 | +#### Testing Requirements |
| 115 | +For each new filter type: |
| 116 | +1. Unit tests in `DatabaseQueryFiltersTest.kt` for all operations |
| 117 | +2. Builder pattern tests in `DataSourceQueryBuilderTest.kt` |
| 118 | +3. JSON serialization validation |
| 119 | +4. Integration test examples (if applicable) |
| 120 | + |
| 121 | +#### Documentation Updates |
| 122 | +- Update examples in test files |
| 123 | +- Add to any relevant documentation |
| 124 | + |
| 125 | +### ✅ Implementation Complete |
| 126 | + |
| 127 | +#### Phase 1-2: Core Missing Types Implemented |
| 128 | +All high-priority and medium-priority filter types have been successfully implemented: |
| 129 | + |
| 130 | +1. ✅ **Relation filters** - `relation(propertyName)` |
| 131 | + - Operations: `contains(pageId)`, `doesNotContain(pageId)`, `isEmpty()`, `isNotEmpty()` |
| 132 | + - Use case: Filter by related pages/entities |
| 133 | + |
| 134 | +2. ✅ **People filters** - `people(propertyName)` |
| 135 | + - Operations: `contains(userId)`, `doesNotContain(userId)`, `isEmpty()`, `isNotEmpty()` |
| 136 | + - Use case: Filter by assignees, collaborators, creators |
| 137 | + |
| 138 | +3. ✅ **Status filters** - `status(propertyName)` |
| 139 | + - Operations: `equals(value)`, `doesNotEqual(value)`, `isEmpty()`, `isNotEmpty()` |
| 140 | + - Use case: Filter by workflow status |
| 141 | + |
| 142 | +4. ✅ **Unique ID filters** - `uniqueId(propertyName)` |
| 143 | + - Operations: `equals(value)`, `doesNotEqual(value)`, `greaterThan(value)`, `lessThan(value)`, `greaterThanOrEqualTo(value)`, `lessThanOrEqualTo(value)` |
| 144 | + - Use case: Range queries on auto-incrementing IDs |
| 145 | + |
| 146 | +5. ✅ **Files filters** - `files(propertyName)` |
| 147 | + - Operations: `isEmpty()`, `isNotEmpty()` |
| 148 | + - Use case: Check for attachment presence |
| 149 | + |
| 150 | +#### Implementation Details |
| 151 | + |
| 152 | +**Data Models Added** (`src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQuery.kt`): |
| 153 | +- `RelationCondition` - for relation property filters |
| 154 | +- `PeopleCondition` - for people property filters |
| 155 | +- `StatusCondition` - for status property filters |
| 156 | +- `UniqueIdCondition` - for unique_id property filters (uses Int, not UUID) |
| 157 | +- `FilesCondition` - for files property filters |
| 158 | + |
| 159 | +**Builder Classes Added** (`src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQueryBuilder.kt`): |
| 160 | +- `RelationFilterBuilder` - DSL builder for relation filters |
| 161 | +- `PeopleFilterBuilder` - DSL builder for people filters |
| 162 | +- `StatusFilterBuilder` - DSL builder for status filters |
| 163 | +- `UniqueIdFilterBuilder` - DSL builder for unique_id filters |
| 164 | +- `FilesFilterBuilder` - DSL builder for files filters |
| 165 | + |
| 166 | +**FilterBuilder Extensions**: |
| 167 | +- Added `relation()`, `people()`, `status()`, `uniqueId()`, `files()` methods to FilterBuilder |
| 168 | + |
| 169 | +**Tests Added**: |
| 170 | +- `src/test/kotlin/unit/query/DatabaseQueryFiltersTest.kt` - 7 new unit test cases covering all new filter types |
| 171 | +- `src/test/kotlin/integration/NewFilterTypesIntegrationTest.kt` - Comprehensive integration test suite for manual verification |
| 172 | + |
| 173 | +#### Usage Examples |
| 174 | + |
| 175 | +```kotlin |
| 176 | +// Filter by relation |
| 177 | +dataSourceQuery { |
| 178 | + filter { |
| 179 | + relation("Project").contains("12345678-1234-1234-1234-123456789abc") |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +// Filter by people (assignees) |
| 184 | +dataSourceQuery { |
| 185 | + filter { |
| 186 | + and( |
| 187 | + people("Assignee").contains("user-uuid"), |
| 188 | + people("Collaborators").isNotEmpty() |
| 189 | + ) |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +// Filter by status |
| 194 | +dataSourceQuery { |
| 195 | + filter { |
| 196 | + status("Status").equals("In Progress") |
| 197 | + } |
| 198 | +} |
| 199 | + |
| 200 | +// Filter by unique_id (range query) |
| 201 | +dataSourceQuery { |
| 202 | + filter { |
| 203 | + and( |
| 204 | + uniqueId("ID").greaterThan(100), |
| 205 | + uniqueId("ID").lessThanOrEqualTo(999) |
| 206 | + ) |
| 207 | + } |
| 208 | +} |
| 209 | + |
| 210 | +// Filter by files presence |
| 211 | +dataSourceQuery { |
| 212 | + filter { |
| 213 | + files("Attachments").isNotEmpty() |
| 214 | + } |
| 215 | +} |
| 216 | + |
| 217 | +// Complex query with multiple new filter types |
| 218 | +dataSourceQuery { |
| 219 | + filter { |
| 220 | + and( |
| 221 | + title("Task Name").contains("Feature"), |
| 222 | + status("Status").equals("Active"), |
| 223 | + people("Assignee").isNotEmpty(), |
| 224 | + relation("Epic").contains("epic-page-id"), |
| 225 | + uniqueId("ID").greaterThan(1), |
| 226 | + files("Screenshots").isNotEmpty() |
| 227 | + ) |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +#### Testing & Verification |
| 233 | + |
| 234 | +**Unit Tests**: ✅ All passing |
| 235 | +- 7 new test cases in `DatabaseQueryFiltersTest.kt` |
| 236 | +- Tests for individual filter types and complex combinations |
| 237 | +- Run with: `./gradlew test` |
| 238 | + |
| 239 | +**Integration Tests**: ✅ Manually verified against live Notion API |
| 240 | +- Created comprehensive test suite with 3-phase workflow: |
| 241 | + 1. Setup: Creates test databases with all new property types |
| 242 | + 2. Populate: Creates sample pages with test data |
| 243 | + 3. Verify: Tests each filter type against real API |
| 244 | +- All filter types verified working correctly with live data |
| 245 | +- Run with: `./gradlew test --tests "*NewFilterTypesIntegrationTest*Verify*"` |
| 246 | + |
| 247 | +**Build Results**: |
| 248 | +- ✅ Code formatting passed |
| 249 | +- ✅ Build successful |
| 250 | +- ✅ All tests passing |
| 251 | + |
| 252 | +#### Remaining Work (Lower Priority) |
| 253 | +- Timestamp filters (special case - no property name, filters by created_time/last_edited_time) |
| 254 | +- Formula filters (nested structure based on formula result type) |
| 255 | +- Rollup filters (most complex - nested with multiple aggregation modes) |
| 256 | + |
| 257 | +These can be implemented later as needed. The most commonly used filter types are now fully supported. |
| 258 | + |
| 259 | +### 📚 Reference |
| 260 | +- Notion API Filter Documentation: `reference/notion-api/documentation/general/07_Filter_Database_Entries.md` |
| 261 | +- Implementation: `src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/` |
| 262 | +- Unit Tests: `src/test/kotlin/unit/query/DatabaseQueryFiltersTest.kt` |
| 263 | +- Integration Tests: `src/test/kotlin/integration/NewFilterTypesIntegrationTest.kt` |
0 commit comments