mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Merge pull request #420 from jkurei/master
Querying by dateCreated and dateModified virtual attributes
This commit is contained in:
		
							
								
								
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -2061,6 +2061,11 @@ | ||||
|       "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", | ||||
|       "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" | ||||
|     }, | ||||
|     "dayjs": { | ||||
|       "version": "1.8.6", | ||||
|       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.6.tgz", | ||||
|       "integrity": "sha512-NLhaSS1/wWLRFy0Kn/VmsAExqll2zxRUPmPbqJoeMKQrFxG+RT94VMSE+cVljB6A76/zZkR0Xub4ihTHQ5HgGg==" | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", | ||||
|   | ||||
| @@ -27,6 +27,7 @@ | ||||
|     "cls-hooked": "4.2.2", | ||||
|     "commonmark": "0.28.1", | ||||
|     "cookie-parser": "1.4.4", | ||||
|     "dayjs": "^1.8.5", | ||||
|     "debug": "4.1.1", | ||||
|     "ejs": "2.6.1", | ||||
|     "electron-debug": "2.1.0", | ||||
|   | ||||
| @@ -1,3 +1,18 @@ | ||||
| function isVirtualAttribute(filter) { | ||||
|     return ( | ||||
|         filter.name == "dateModified" | ||||
|         || filter.name == "dateCreated" | ||||
|         || filter.name == "isProtected" | ||||
|     ); | ||||
| } | ||||
|  | ||||
| function getValueForFilter(filter, i) { | ||||
|     return (isVirtualAttribute(filter) | ||||
|         ? `substr(notes.${filter.name}, 0, ${filter.value.length + 1})` | ||||
|         :`attribute${i}.value` | ||||
|     ); | ||||
| } | ||||
|  | ||||
| module.exports = function(attributeFilters) { | ||||
|     const joins = []; | ||||
|     const joinParams = []; | ||||
| @@ -7,23 +22,34 @@ module.exports = function(attributeFilters) { | ||||
|     let i = 1; | ||||
|  | ||||
|     for (const filter of attributeFilters) { | ||||
|         joins.push(`LEFT JOIN attributes AS attribute${i} ON attribute${i}.noteId = notes.noteId AND attribute${i}.name = ? AND attribute${i}.isDeleted = 0`); | ||||
|         joinParams.push(filter.name); | ||||
|         const virtual = isVirtualAttribute(filter); | ||||
|  | ||||
|         if (!virtual) { | ||||
|             joins.push(`LEFT JOIN attributes AS attribute${i} ` | ||||
|                 + `ON attribute${i}.noteId = notes.noteId ` | ||||
|                 + `AND attribute${i}.name = ? AND attribute${i}.isDeleted = 0` | ||||
|             ); | ||||
|             joinParams.push(filter.name); | ||||
|         } | ||||
|  | ||||
|         where += " " + filter.relation + " "; | ||||
|  | ||||
|         // the value we need to test | ||||
|         const test = virtual ? filter.name : `attribute${i}.attributeId`; | ||||
|  | ||||
|         if (filter.operator === 'exists') { | ||||
|             where += `attribute${i}.attributeId IS NOT NULL`; | ||||
|             where += `${test} IS NOT NULL`; | ||||
|         } | ||||
|         else if (filter.operator === 'not-exists') { | ||||
|             where += `attribute${i}.attributeId IS NULL`; | ||||
|             where += `${test} IS NULL`; | ||||
|         } | ||||
|         else if (filter.operator === '=' || filter.operator === '!=') { | ||||
|             where += `attribute${i}.value ${filter.operator} ?`; | ||||
|             where += `${getValueForFilter(filter, i)} ${filter.operator} ?`; | ||||
|             whereParams.push(filter.value); | ||||
|         } | ||||
|         else if ([">", ">=", "<", "<="].includes(filter.operator)) { | ||||
|             let floatParam; | ||||
|             const value = getValueForFilter(filter, i); | ||||
|  | ||||
|             // from https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers | ||||
|             if (/^[+-]?([0-9]*[.])?[0-9]+$/.test(filter.value)) { | ||||
| @@ -32,11 +58,11 @@ module.exports = function(attributeFilters) { | ||||
|  | ||||
|             if (floatParam === undefined || isNaN(floatParam)) { | ||||
|                 // if the value can't be parsed as float then we assume that string comparison should be used instead of numeric | ||||
|                 where += `attribute${i}.value ${filter.operator} ?`; | ||||
|                 where += `${value} ${filter.operator} ?`; | ||||
|                 whereParams.push(filter.value); | ||||
|             } | ||||
|             else { | ||||
|                 where += `CAST(attribute${i}.value AS DECIMAL) ${filter.operator} ?`; | ||||
|                 where += `CAST(${value} AS DECIMAL) ${filter.operator} ?`; | ||||
|                 whereParams.push(floatParam); | ||||
|             } | ||||
|         } | ||||
| @@ -52,12 +78,12 @@ module.exports = function(attributeFilters) { | ||||
|  | ||||
|     const query = `SELECT DISTINCT notes.noteId FROM notes | ||||
|             ${joins.join('\r\n')} | ||||
|               WHERE  | ||||
|               WHERE | ||||
|                 notes.isDeleted = 0 | ||||
|                 AND (${where})  | ||||
|                 AND (${where}) | ||||
|                 ${searchCondition}`; | ||||
|  | ||||
|     const params = joinParams.concat(whereParams).concat(searchParams); | ||||
|  | ||||
|     return { query, params }; | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -1,8 +1,30 @@ | ||||
| const dayjs = require("dayjs"); | ||||
|  | ||||
| const labelRegex = /(\b(and|or)\s+)?@(!?)([\w_-]+|"[^"]+")((=|!=|<|<=|>|>=)([\w_-]+|"[^"]+"))?/i; | ||||
| const smartValueRegex = /^(TODAY|NOW)((\+|\-)(\d+)(H|D|M|Y)){0,1}$/i; | ||||
|  | ||||
| function calculateSmartValue(v) { | ||||
|     const normalizedV = v.toUpperCase() + "+0D"; // defaults of sorts | ||||
|     const [ , keyword, sign, snum, unit] = /(TODAY|NOW)(\+|\-)(\d+)(H|D|M|Y)/.exec(normalizedV); | ||||
|     const num = parseInt(snum); | ||||
|  | ||||
|     if (keyword != "TODAY" && keyword != "NOW") { | ||||
|         return v; | ||||
|     } | ||||
|  | ||||
|     const fullUnit = { | ||||
|         TODAY: { D: "days", M: "months", Y: "years" }, | ||||
|         NOW: { D: "days", M: "minutes", H: "hours" } | ||||
|     }[keyword][unit]; | ||||
|  | ||||
|     const format = keyword == "TODAY" ? "YYYY-MM-DD" : "YYYY-MM-DDTHH:mm"; | ||||
|     const date = (sign == "+" ? dayjs().add(num, fullUnit) : dayjs().subtract(num, fullUnit)); | ||||
|  | ||||
|     return date.format(format); | ||||
| } | ||||
|  | ||||
| module.exports = function(searchText) { | ||||
|     const labelFilters = []; | ||||
|  | ||||
|     const labelRegex = /(\b(and|or)\s+)?@(!?)([\w_-]+|"[^"]+")((=|!=|<|<=|>|>=)([\w_-]+|"[^"]+"))?/i; | ||||
|  | ||||
|     let match = labelRegex.exec(searchText); | ||||
|  | ||||
|     function trimQuotes(str) { return str.startsWith('"') ? str.substr(1, str.length - 2) : str; } | ||||
| @@ -11,11 +33,17 @@ module.exports = function(searchText) { | ||||
|         const relation = match[2] !== undefined ? match[2].toLowerCase() : 'and'; | ||||
|         const operator = match[3] === '!' ? 'not-exists' : 'exists'; | ||||
|  | ||||
|         const value = match[7] !== undefined ? trimQuotes(match[7]) : null | ||||
|  | ||||
|         labelFilters.push({ | ||||
|             relation: relation, | ||||
|             name: trimQuotes(match[4]), | ||||
|             operator: match[6] !== undefined ? match[6] : operator, | ||||
|             value: match[7] !== undefined ? trimQuotes(match[7]) : null | ||||
|             value: ( | ||||
|                 value && value.match(smartValueRegex) | ||||
|                     ? calculateSmartValue(value) | ||||
|                     : value | ||||
|             ) | ||||
|         }); | ||||
|  | ||||
|         // remove labels from further fulltext search | ||||
| @@ -24,5 +52,5 @@ module.exports = function(searchText) { | ||||
|         match = labelRegex.exec(searchText); | ||||
|     } | ||||
|  | ||||
|     return {labelFilters: labelFilters, searchText}; | ||||
| }; | ||||
|     return { labelFilters: labelFilters, searchText }; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user