search-input-query-parser 0.5.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/search-query-to-ilike-sql.js +49 -2
- package/dist/cjs/search-query-to-paradedb-sql.js +49 -2
- package/dist/cjs/search-query-to-tsvector-sql.js +49 -2
- package/dist/cjs/validate-expression-fields.js +12 -0
- package/dist/esm/search-query-to-ilike-sql.js +49 -2
- package/dist/esm/search-query-to-paradedb-sql.js +49 -2
- package/dist/esm/search-query-to-tsvector-sql.js +49 -2
- package/dist/esm/validate-expression-fields.js +12 -0
- package/package.json +1 -1
- package/src/parser.test.ts +80 -1
- package/src/search-query-to-ilike-sql.ts +50 -2
- package/src/search-query-to-paradedb-sql.test.ts +62 -0
- package/src/search-query-to-paradedb-sql.ts +50 -2
- package/src/search-query-to-sql.test.ts +60 -0
- package/src/search-query-to-tsvector-sql.ts +50 -2
- package/src/validate-expression-fields.ts +15 -0
|
@@ -86,6 +86,27 @@ const fieldValueToSql = (field, value, state) => {
|
|
|
86
86
|
// Rest of the function remains the same...
|
|
87
87
|
switch (schema === null || schema === void 0 ? void 0 : schema.type) {
|
|
88
88
|
case "date":
|
|
89
|
+
// Handle year format (YYYY)
|
|
90
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
91
|
+
const year = cleanedValue;
|
|
92
|
+
const [param1, state1] = nextParam(state);
|
|
93
|
+
const [param2, state2] = nextParam(state1);
|
|
94
|
+
return [
|
|
95
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
96
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
// Handle year-month format (YYYY-MM)
|
|
100
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
101
|
+
const [year, month] = cleanedValue.split('-');
|
|
102
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
103
|
+
const [param1, state1] = nextParam(state);
|
|
104
|
+
const [param2, state2] = nextParam(state1);
|
|
105
|
+
return [
|
|
106
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
107
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
89
110
|
return [
|
|
90
111
|
`${field} = ${paramName}`,
|
|
91
112
|
addValue(newState, cleanedValue),
|
|
@@ -120,10 +141,36 @@ const rangeToSql = (field, operator, value, value2, state) => {
|
|
|
120
141
|
];
|
|
121
142
|
}
|
|
122
143
|
const [paramName, newState] = nextParam(state);
|
|
123
|
-
|
|
144
|
+
let val = value;
|
|
145
|
+
// Handle date shorthand formats in comparison operators
|
|
146
|
+
if (isDateField) {
|
|
147
|
+
// Year format (YYYY)
|
|
148
|
+
if (/^\d{4}$/.test(value)) {
|
|
149
|
+
if (operator === ">" || operator === ">=") {
|
|
150
|
+
val = `${value}-01-01`;
|
|
151
|
+
}
|
|
152
|
+
else if (operator === "<" || operator === "<=") {
|
|
153
|
+
val = `${value}-12-31`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Month format (YYYY-MM)
|
|
157
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
158
|
+
const [year, month] = value.split('-');
|
|
159
|
+
if (operator === ">" || operator === ">=") {
|
|
160
|
+
val = `${year}-${month}-01`;
|
|
161
|
+
}
|
|
162
|
+
else if (operator === "<" || operator === "<=") {
|
|
163
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
164
|
+
val = `${year}-${month}-${lastDay}`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
val = value; // Keep as string, will be converted to number later
|
|
170
|
+
}
|
|
124
171
|
return [
|
|
125
172
|
`${field} ${operator} ${paramName}`,
|
|
126
|
-
addValue(newState, val),
|
|
173
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
127
174
|
];
|
|
128
175
|
};
|
|
129
176
|
const inExpressionToSql = (field, values, state) => {
|
|
@@ -109,6 +109,27 @@ const fieldValueToSql = (field, value, state) => {
|
|
|
109
109
|
const baseValue = hasWildcard ? cleanedValue.slice(0, -1) : cleanedValue;
|
|
110
110
|
switch (schema === null || schema === void 0 ? void 0 : schema.type) {
|
|
111
111
|
case "date": {
|
|
112
|
+
// Handle year format (YYYY)
|
|
113
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
114
|
+
const year = cleanedValue;
|
|
115
|
+
const [param1, state1] = nextParam(state);
|
|
116
|
+
const [param2, state2] = nextParam(state1);
|
|
117
|
+
return [
|
|
118
|
+
`${field} @@@ '[' || ${param1} || ' TO ' || ${param2} || ']'`,
|
|
119
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
// Handle year-month format (YYYY-MM)
|
|
123
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
124
|
+
const [year, month] = cleanedValue.split('-');
|
|
125
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
126
|
+
const [param1, state1] = nextParam(state);
|
|
127
|
+
const [param2, state2] = nextParam(state1);
|
|
128
|
+
return [
|
|
129
|
+
`${field} @@@ '[' || ${param1} || ' TO ' || ${param2} || ']'`,
|
|
130
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
131
|
+
];
|
|
132
|
+
}
|
|
112
133
|
// Use parameter binding for dates
|
|
113
134
|
const [dateParam, dateState] = nextParam(state);
|
|
114
135
|
return [
|
|
@@ -142,11 +163,37 @@ const rangeToSql = (field, operator, value, value2, state) => {
|
|
|
142
163
|
}
|
|
143
164
|
else {
|
|
144
165
|
const [paramName, newState] = nextParam(state);
|
|
166
|
+
let val = value;
|
|
167
|
+
// Handle date shorthand formats in comparison operators
|
|
168
|
+
if (isDateField) {
|
|
169
|
+
// Year format (YYYY)
|
|
170
|
+
if (/^\d{4}$/.test(value)) {
|
|
171
|
+
if (operator === ">" || operator === ">=") {
|
|
172
|
+
val = `${value}-01-01`;
|
|
173
|
+
}
|
|
174
|
+
else if (operator === "<" || operator === "<=") {
|
|
175
|
+
val = `${value}-12-31`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Month format (YYYY-MM)
|
|
179
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
180
|
+
const [year, month] = value.split('-');
|
|
181
|
+
if (operator === ">" || operator === ">=") {
|
|
182
|
+
val = `${year}-${month}-01`;
|
|
183
|
+
}
|
|
184
|
+
else if (operator === "<" || operator === "<=") {
|
|
185
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
186
|
+
val = `${year}-${month}-${lastDay}`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
val = value; // Keep as string, will be converted to number later
|
|
192
|
+
}
|
|
145
193
|
const rangeOp = operator.replace(">=", ">=").replace("<=", "<=");
|
|
146
|
-
const val = isDateField ? value : Number(value);
|
|
147
194
|
return [
|
|
148
195
|
`${field} @@@ '${rangeOp}' || ${paramName}`,
|
|
149
|
-
addValue(newState, val),
|
|
196
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
150
197
|
];
|
|
151
198
|
}
|
|
152
199
|
};
|
|
@@ -78,6 +78,27 @@ const fieldValueToSql = (field, value, state) => {
|
|
|
78
78
|
// Rest of the function remains the same...
|
|
79
79
|
switch (schema === null || schema === void 0 ? void 0 : schema.type) {
|
|
80
80
|
case "date":
|
|
81
|
+
// Handle year format (YYYY)
|
|
82
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
83
|
+
const year = cleanedValue;
|
|
84
|
+
const [param1, state1] = nextParam(state);
|
|
85
|
+
const [param2, state2] = nextParam(state1);
|
|
86
|
+
return [
|
|
87
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
88
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
// Handle year-month format (YYYY-MM)
|
|
92
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
93
|
+
const [year, month] = cleanedValue.split('-');
|
|
94
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
95
|
+
const [param1, state1] = nextParam(state);
|
|
96
|
+
const [param2, state2] = nextParam(state1);
|
|
97
|
+
return [
|
|
98
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
99
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
81
102
|
return [
|
|
82
103
|
`${field} = ${paramName}`,
|
|
83
104
|
addValue(newState, cleanedValue),
|
|
@@ -112,10 +133,36 @@ const rangeToSql = (field, operator, value, value2, state) => {
|
|
|
112
133
|
];
|
|
113
134
|
}
|
|
114
135
|
const [paramName, newState] = nextParam(state);
|
|
115
|
-
|
|
136
|
+
let val = value;
|
|
137
|
+
// Handle date shorthand formats in comparison operators
|
|
138
|
+
if (isDateField) {
|
|
139
|
+
// Year format (YYYY)
|
|
140
|
+
if (/^\d{4}$/.test(value)) {
|
|
141
|
+
if (operator === ">" || operator === ">=") {
|
|
142
|
+
val = `${value}-01-01`;
|
|
143
|
+
}
|
|
144
|
+
else if (operator === "<" || operator === "<=") {
|
|
145
|
+
val = `${value}-12-31`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Month format (YYYY-MM)
|
|
149
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
150
|
+
const [year, month] = value.split('-');
|
|
151
|
+
if (operator === ">" || operator === ">=") {
|
|
152
|
+
val = `${year}-${month}-01`;
|
|
153
|
+
}
|
|
154
|
+
else if (operator === "<" || operator === "<=") {
|
|
155
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
156
|
+
val = `${year}-${month}-${lastDay}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
val = value; // Keep as string, will be converted to number later
|
|
162
|
+
}
|
|
116
163
|
return [
|
|
117
164
|
`${field} ${operator} ${paramName}`,
|
|
118
|
-
addValue(newState, val),
|
|
165
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
119
166
|
];
|
|
120
167
|
};
|
|
121
168
|
const inExpressionToSql = (field, values, state) => {
|
|
@@ -208,6 +208,17 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
208
208
|
const dateValidator = (dateStr) => {
|
|
209
209
|
if (!dateStr)
|
|
210
210
|
return true;
|
|
211
|
+
// Check for year format (YYYY)
|
|
212
|
+
if (/^\d{4}$/.test(dateStr)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
// Check for year-month format (YYYY-MM)
|
|
216
|
+
if (/^\d{4}-\d{2}$/.test(dateStr)) {
|
|
217
|
+
const [year, month] = dateStr.split('-').map(Number);
|
|
218
|
+
// Validate month is between 1-12
|
|
219
|
+
return month >= 1 && month <= 12;
|
|
220
|
+
}
|
|
221
|
+
// Check for full date format (YYYY-MM-DD)
|
|
211
222
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
|
212
223
|
return false;
|
|
213
224
|
}
|
|
@@ -215,6 +226,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
215
226
|
return (!isNaN(date.getTime()) &&
|
|
216
227
|
dateStr === date.toISOString().split("T")[0]);
|
|
217
228
|
};
|
|
229
|
+
// Handle date range with shorthand formats
|
|
218
230
|
if (value.includes("..")) {
|
|
219
231
|
const [start, end] = value.split("..");
|
|
220
232
|
if (!dateValidator(start) || !dateValidator(end)) {
|
|
@@ -83,6 +83,27 @@ const fieldValueToSql = (field, value, state) => {
|
|
|
83
83
|
// Rest of the function remains the same...
|
|
84
84
|
switch (schema === null || schema === void 0 ? void 0 : schema.type) {
|
|
85
85
|
case "date":
|
|
86
|
+
// Handle year format (YYYY)
|
|
87
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
88
|
+
const year = cleanedValue;
|
|
89
|
+
const [param1, state1] = nextParam(state);
|
|
90
|
+
const [param2, state2] = nextParam(state1);
|
|
91
|
+
return [
|
|
92
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
93
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
// Handle year-month format (YYYY-MM)
|
|
97
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
98
|
+
const [year, month] = cleanedValue.split('-');
|
|
99
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
100
|
+
const [param1, state1] = nextParam(state);
|
|
101
|
+
const [param2, state2] = nextParam(state1);
|
|
102
|
+
return [
|
|
103
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
104
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
105
|
+
];
|
|
106
|
+
}
|
|
86
107
|
return [
|
|
87
108
|
`${field} = ${paramName}`,
|
|
88
109
|
addValue(newState, cleanedValue),
|
|
@@ -117,10 +138,36 @@ const rangeToSql = (field, operator, value, value2, state) => {
|
|
|
117
138
|
];
|
|
118
139
|
}
|
|
119
140
|
const [paramName, newState] = nextParam(state);
|
|
120
|
-
|
|
141
|
+
let val = value;
|
|
142
|
+
// Handle date shorthand formats in comparison operators
|
|
143
|
+
if (isDateField) {
|
|
144
|
+
// Year format (YYYY)
|
|
145
|
+
if (/^\d{4}$/.test(value)) {
|
|
146
|
+
if (operator === ">" || operator === ">=") {
|
|
147
|
+
val = `${value}-01-01`;
|
|
148
|
+
}
|
|
149
|
+
else if (operator === "<" || operator === "<=") {
|
|
150
|
+
val = `${value}-12-31`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Month format (YYYY-MM)
|
|
154
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
155
|
+
const [year, month] = value.split('-');
|
|
156
|
+
if (operator === ">" || operator === ">=") {
|
|
157
|
+
val = `${year}-${month}-01`;
|
|
158
|
+
}
|
|
159
|
+
else if (operator === "<" || operator === "<=") {
|
|
160
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
161
|
+
val = `${year}-${month}-${lastDay}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
val = value; // Keep as string, will be converted to number later
|
|
167
|
+
}
|
|
121
168
|
return [
|
|
122
169
|
`${field} ${operator} ${paramName}`,
|
|
123
|
-
addValue(newState, val),
|
|
170
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
124
171
|
];
|
|
125
172
|
};
|
|
126
173
|
const inExpressionToSql = (field, values, state) => {
|
|
@@ -106,6 +106,27 @@ const fieldValueToSql = (field, value, state) => {
|
|
|
106
106
|
const baseValue = hasWildcard ? cleanedValue.slice(0, -1) : cleanedValue;
|
|
107
107
|
switch (schema === null || schema === void 0 ? void 0 : schema.type) {
|
|
108
108
|
case "date": {
|
|
109
|
+
// Handle year format (YYYY)
|
|
110
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
111
|
+
const year = cleanedValue;
|
|
112
|
+
const [param1, state1] = nextParam(state);
|
|
113
|
+
const [param2, state2] = nextParam(state1);
|
|
114
|
+
return [
|
|
115
|
+
`${field} @@@ '[' || ${param1} || ' TO ' || ${param2} || ']'`,
|
|
116
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
// Handle year-month format (YYYY-MM)
|
|
120
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
121
|
+
const [year, month] = cleanedValue.split('-');
|
|
122
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
123
|
+
const [param1, state1] = nextParam(state);
|
|
124
|
+
const [param2, state2] = nextParam(state1);
|
|
125
|
+
return [
|
|
126
|
+
`${field} @@@ '[' || ${param1} || ' TO ' || ${param2} || ']'`,
|
|
127
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
128
|
+
];
|
|
129
|
+
}
|
|
109
130
|
// Use parameter binding for dates
|
|
110
131
|
const [dateParam, dateState] = nextParam(state);
|
|
111
132
|
return [
|
|
@@ -139,11 +160,37 @@ const rangeToSql = (field, operator, value, value2, state) => {
|
|
|
139
160
|
}
|
|
140
161
|
else {
|
|
141
162
|
const [paramName, newState] = nextParam(state);
|
|
163
|
+
let val = value;
|
|
164
|
+
// Handle date shorthand formats in comparison operators
|
|
165
|
+
if (isDateField) {
|
|
166
|
+
// Year format (YYYY)
|
|
167
|
+
if (/^\d{4}$/.test(value)) {
|
|
168
|
+
if (operator === ">" || operator === ">=") {
|
|
169
|
+
val = `${value}-01-01`;
|
|
170
|
+
}
|
|
171
|
+
else if (operator === "<" || operator === "<=") {
|
|
172
|
+
val = `${value}-12-31`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Month format (YYYY-MM)
|
|
176
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
177
|
+
const [year, month] = value.split('-');
|
|
178
|
+
if (operator === ">" || operator === ">=") {
|
|
179
|
+
val = `${year}-${month}-01`;
|
|
180
|
+
}
|
|
181
|
+
else if (operator === "<" || operator === "<=") {
|
|
182
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
183
|
+
val = `${year}-${month}-${lastDay}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
val = value; // Keep as string, will be converted to number later
|
|
189
|
+
}
|
|
142
190
|
const rangeOp = operator.replace(">=", ">=").replace("<=", "<=");
|
|
143
|
-
const val = isDateField ? value : Number(value);
|
|
144
191
|
return [
|
|
145
192
|
`${field} @@@ '${rangeOp}' || ${paramName}`,
|
|
146
|
-
addValue(newState, val),
|
|
193
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
147
194
|
];
|
|
148
195
|
}
|
|
149
196
|
};
|
|
@@ -75,6 +75,27 @@ const fieldValueToSql = (field, value, state) => {
|
|
|
75
75
|
// Rest of the function remains the same...
|
|
76
76
|
switch (schema === null || schema === void 0 ? void 0 : schema.type) {
|
|
77
77
|
case "date":
|
|
78
|
+
// Handle year format (YYYY)
|
|
79
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
80
|
+
const year = cleanedValue;
|
|
81
|
+
const [param1, state1] = nextParam(state);
|
|
82
|
+
const [param2, state2] = nextParam(state1);
|
|
83
|
+
return [
|
|
84
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
85
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
// Handle year-month format (YYYY-MM)
|
|
89
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
90
|
+
const [year, month] = cleanedValue.split('-');
|
|
91
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
92
|
+
const [param1, state1] = nextParam(state);
|
|
93
|
+
const [param2, state2] = nextParam(state1);
|
|
94
|
+
return [
|
|
95
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
96
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
78
99
|
return [
|
|
79
100
|
`${field} = ${paramName}`,
|
|
80
101
|
addValue(newState, cleanedValue),
|
|
@@ -109,10 +130,36 @@ const rangeToSql = (field, operator, value, value2, state) => {
|
|
|
109
130
|
];
|
|
110
131
|
}
|
|
111
132
|
const [paramName, newState] = nextParam(state);
|
|
112
|
-
|
|
133
|
+
let val = value;
|
|
134
|
+
// Handle date shorthand formats in comparison operators
|
|
135
|
+
if (isDateField) {
|
|
136
|
+
// Year format (YYYY)
|
|
137
|
+
if (/^\d{4}$/.test(value)) {
|
|
138
|
+
if (operator === ">" || operator === ">=") {
|
|
139
|
+
val = `${value}-01-01`;
|
|
140
|
+
}
|
|
141
|
+
else if (operator === "<" || operator === "<=") {
|
|
142
|
+
val = `${value}-12-31`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Month format (YYYY-MM)
|
|
146
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
147
|
+
const [year, month] = value.split('-');
|
|
148
|
+
if (operator === ">" || operator === ">=") {
|
|
149
|
+
val = `${year}-${month}-01`;
|
|
150
|
+
}
|
|
151
|
+
else if (operator === "<" || operator === "<=") {
|
|
152
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
153
|
+
val = `${year}-${month}-${lastDay}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
val = value; // Keep as string, will be converted to number later
|
|
159
|
+
}
|
|
113
160
|
return [
|
|
114
161
|
`${field} ${operator} ${paramName}`,
|
|
115
|
-
addValue(newState, val),
|
|
162
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
116
163
|
];
|
|
117
164
|
};
|
|
118
165
|
const inExpressionToSql = (field, values, state) => {
|
|
@@ -205,6 +205,17 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
205
205
|
const dateValidator = (dateStr) => {
|
|
206
206
|
if (!dateStr)
|
|
207
207
|
return true;
|
|
208
|
+
// Check for year format (YYYY)
|
|
209
|
+
if (/^\d{4}$/.test(dateStr)) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
// Check for year-month format (YYYY-MM)
|
|
213
|
+
if (/^\d{4}-\d{2}$/.test(dateStr)) {
|
|
214
|
+
const [year, month] = dateStr.split('-').map(Number);
|
|
215
|
+
// Validate month is between 1-12
|
|
216
|
+
return month >= 1 && month <= 12;
|
|
217
|
+
}
|
|
218
|
+
// Check for full date format (YYYY-MM-DD)
|
|
208
219
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
|
209
220
|
return false;
|
|
210
221
|
}
|
|
@@ -212,6 +223,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
212
223
|
return (!isNaN(date.getTime()) &&
|
|
213
224
|
dateStr === date.toISOString().split("T")[0]);
|
|
214
225
|
};
|
|
226
|
+
// Handle date range with shorthand formats
|
|
215
227
|
if (value.includes("..")) {
|
|
216
228
|
const [start, end] = value.split("..");
|
|
217
229
|
if (!dateValidator(start) || !dateValidator(end)) {
|
package/package.json
CHANGED
package/src/parser.test.ts
CHANGED
|
@@ -432,6 +432,44 @@ describe("Search Query Parser", () => {
|
|
|
432
432
|
"date:2023-12-31..2024-01-01"
|
|
433
433
|
);
|
|
434
434
|
});
|
|
435
|
+
|
|
436
|
+
test("parses date shorthand formats", () => {
|
|
437
|
+
// Year shorthand
|
|
438
|
+
testSchemaQuery(
|
|
439
|
+
"date:2024",
|
|
440
|
+
"date:2024"
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Month shorthand
|
|
444
|
+
testSchemaQuery(
|
|
445
|
+
"date:2024-02",
|
|
446
|
+
"date:2024-02"
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
// Shorthand with comparison operators
|
|
450
|
+
testSchemaQuery(
|
|
451
|
+
"date:>=2024",
|
|
452
|
+
"date:>=2024"
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
testSchemaQuery(
|
|
456
|
+
"date:<2024-03",
|
|
457
|
+
"date:<2024-03"
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
// Multiple date shorthands in one query
|
|
461
|
+
testSchemaQuery(
|
|
462
|
+
"date:>=2024 AND date:<2025",
|
|
463
|
+
"(date:>=2024 AND date:<2025)"
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
// Mix of shorthand and full date formats
|
|
467
|
+
testSchemaQuery(
|
|
468
|
+
"date:2024 OR date:2024-01-15",
|
|
469
|
+
"(date:2024 OR date:2024-01-15)"
|
|
470
|
+
);
|
|
471
|
+
});
|
|
472
|
+
|
|
435
473
|
describe("Wildcard Pattern Support", () => {
|
|
436
474
|
test("parses simple wildcard patterns", () => {
|
|
437
475
|
testValidQuery("test*", "test*");
|
|
@@ -764,7 +802,6 @@ describe("Search Query Parser", () => {
|
|
|
764
802
|
length: 11,
|
|
765
803
|
},
|
|
766
804
|
]);
|
|
767
|
-
|
|
768
805
|
testSchemaErrorQuery("date:2024-13-01..2024-12-31", [
|
|
769
806
|
{
|
|
770
807
|
message: "Invalid date format",
|
|
@@ -775,6 +812,48 @@ describe("Search Query Parser", () => {
|
|
|
775
812
|
]);
|
|
776
813
|
});
|
|
777
814
|
|
|
815
|
+
test("validates date shorthand formats", () => {
|
|
816
|
+
// Invalid year format
|
|
817
|
+
testSchemaErrorQuery("date:202", [
|
|
818
|
+
{
|
|
819
|
+
message: "Invalid date format",
|
|
820
|
+
code: SearchQueryErrorCode.VALUE_DATE_FORMAT_INVALID,
|
|
821
|
+
position: 5,
|
|
822
|
+
length: 3,
|
|
823
|
+
},
|
|
824
|
+
]);
|
|
825
|
+
|
|
826
|
+
// Invalid month format
|
|
827
|
+
testSchemaErrorQuery("date:2024-13", [
|
|
828
|
+
{
|
|
829
|
+
message: "Invalid date format",
|
|
830
|
+
code: SearchQueryErrorCode.VALUE_DATE_FORMAT_INVALID,
|
|
831
|
+
position: 5,
|
|
832
|
+
length: 7,
|
|
833
|
+
},
|
|
834
|
+
]);
|
|
835
|
+
|
|
836
|
+
// Invalid month value
|
|
837
|
+
testSchemaErrorQuery("date:2024-00", [
|
|
838
|
+
{
|
|
839
|
+
message: "Invalid date format",
|
|
840
|
+
code: SearchQueryErrorCode.VALUE_DATE_FORMAT_INVALID,
|
|
841
|
+
position: 5,
|
|
842
|
+
length: 7,
|
|
843
|
+
},
|
|
844
|
+
]);
|
|
845
|
+
|
|
846
|
+
// Invalid comparison with shorthand
|
|
847
|
+
testSchemaErrorQuery("date:>invalid-year", [
|
|
848
|
+
{
|
|
849
|
+
message: "Invalid date format",
|
|
850
|
+
code: SearchQueryErrorCode.VALUE_DATE_FORMAT_INVALID,
|
|
851
|
+
position: 5,
|
|
852
|
+
length: 13,
|
|
853
|
+
},
|
|
854
|
+
]);
|
|
855
|
+
});
|
|
856
|
+
|
|
778
857
|
test("handles invalid boolean values", () => {
|
|
779
858
|
testSchemaErrorQuery("in_stock:maybe", [
|
|
780
859
|
{
|
|
@@ -148,6 +148,29 @@ const fieldValueToSql = (
|
|
|
148
148
|
// Rest of the function remains the same...
|
|
149
149
|
switch (schema?.type) {
|
|
150
150
|
case "date":
|
|
151
|
+
// Handle year format (YYYY)
|
|
152
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
153
|
+
const year = cleanedValue;
|
|
154
|
+
const [param1, state1] = nextParam(state);
|
|
155
|
+
const [param2, state2] = nextParam(state1);
|
|
156
|
+
return [
|
|
157
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
158
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Handle year-month format (YYYY-MM)
|
|
163
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
164
|
+
const [year, month] = cleanedValue.split('-');
|
|
165
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
166
|
+
const [param1, state1] = nextParam(state);
|
|
167
|
+
const [param2, state2] = nextParam(state1);
|
|
168
|
+
return [
|
|
169
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
170
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
151
174
|
return [
|
|
152
175
|
`${field} = ${paramName}`,
|
|
153
176
|
addValue(newState, cleanedValue),
|
|
@@ -194,10 +217,35 @@ const rangeToSql = (
|
|
|
194
217
|
}
|
|
195
218
|
|
|
196
219
|
const [paramName, newState] = nextParam(state);
|
|
197
|
-
|
|
220
|
+
let val = value;
|
|
221
|
+
|
|
222
|
+
// Handle date shorthand formats in comparison operators
|
|
223
|
+
if (isDateField) {
|
|
224
|
+
// Year format (YYYY)
|
|
225
|
+
if (/^\d{4}$/.test(value)) {
|
|
226
|
+
if (operator === ">" || operator === ">=") {
|
|
227
|
+
val = `${value}-01-01`;
|
|
228
|
+
} else if (operator === "<" || operator === "<=") {
|
|
229
|
+
val = `${value}-12-31`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Month format (YYYY-MM)
|
|
233
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
234
|
+
const [year, month] = value.split('-');
|
|
235
|
+
if (operator === ">" || operator === ">=") {
|
|
236
|
+
val = `${year}-${month}-01`;
|
|
237
|
+
} else if (operator === "<" || operator === "<=") {
|
|
238
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
239
|
+
val = `${year}-${month}-${lastDay}`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
val = value; // Keep as string, will be converted to number later
|
|
244
|
+
}
|
|
245
|
+
|
|
198
246
|
return [
|
|
199
247
|
`${field} ${operator} ${paramName}`,
|
|
200
|
-
addValue(newState, val),
|
|
248
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
201
249
|
];
|
|
202
250
|
};
|
|
203
251
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { searchStringToParadeDbSql } from "./search-query-to-paradedb-sql";
|
|
3
|
+
import { FieldSchema } from "./parser";
|
|
4
|
+
|
|
5
|
+
describe("ParadeDB SQL Converter", () => {
|
|
6
|
+
const schemas: FieldSchema[] = [
|
|
7
|
+
{ name: "title", type: "string" },
|
|
8
|
+
{ name: "description", type: "string" },
|
|
9
|
+
{ name: "content", type: "string" },
|
|
10
|
+
{ name: "price", type: "number" },
|
|
11
|
+
{ name: "date", type: "date" },
|
|
12
|
+
{ name: "in_stock", type: "boolean" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const searchableColumns = ["title", "description", "content"];
|
|
16
|
+
|
|
17
|
+
const testParadeDBConversion = (
|
|
18
|
+
query: string,
|
|
19
|
+
expectedSql: string,
|
|
20
|
+
expectedValues: any[]
|
|
21
|
+
) => {
|
|
22
|
+
const result = searchStringToParadeDbSql(
|
|
23
|
+
query,
|
|
24
|
+
searchableColumns,
|
|
25
|
+
schemas
|
|
26
|
+
);
|
|
27
|
+
expect(result.text).toBe(expectedSql);
|
|
28
|
+
expect(result.values).toEqual(expectedValues);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("ParadeDB Date Handling", () => {
|
|
32
|
+
test("handles date year shorthand format", () => {
|
|
33
|
+
testParadeDBConversion(
|
|
34
|
+
"date:2024",
|
|
35
|
+
"date @@@ '[' || $1 || ' TO ' || $2 || ']'",
|
|
36
|
+
["2024-01-01", "2024-12-31"]
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("handles date month shorthand format", () => {
|
|
41
|
+
testParadeDBConversion(
|
|
42
|
+
"date:2024-02",
|
|
43
|
+
"date @@@ '[' || $1 || ' TO ' || $2 || ']'",
|
|
44
|
+
["2024-02-01", "2024-02-29"] // 2024 is a leap year
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
testParadeDBConversion(
|
|
48
|
+
"date:2023-04",
|
|
49
|
+
"date @@@ '[' || $1 || ' TO ' || $2 || ']'",
|
|
50
|
+
["2023-04-01", "2023-04-30"]
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("handles date shorthand formats with comparison operators", () => {
|
|
55
|
+
testParadeDBConversion(
|
|
56
|
+
"date:>=2024 AND date:<2025",
|
|
57
|
+
"(date @@@ '>=' || $1 AND date @@@ '<' || $2)",
|
|
58
|
+
["2024-01-01", "2025-12-31"]
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -174,6 +174,29 @@ const fieldValueToSql = (
|
|
|
174
174
|
|
|
175
175
|
switch (schema?.type) {
|
|
176
176
|
case "date": {
|
|
177
|
+
// Handle year format (YYYY)
|
|
178
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
179
|
+
const year = cleanedValue;
|
|
180
|
+
const [param1, state1] = nextParam(state);
|
|
181
|
+
const [param2, state2] = nextParam(state1);
|
|
182
|
+
return [
|
|
183
|
+
`${field} @@@ '[' || ${param1} || ' TO ' || ${param2} || ']'`,
|
|
184
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Handle year-month format (YYYY-MM)
|
|
189
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
190
|
+
const [year, month] = cleanedValue.split('-');
|
|
191
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
192
|
+
const [param1, state1] = nextParam(state);
|
|
193
|
+
const [param2, state2] = nextParam(state1);
|
|
194
|
+
return [
|
|
195
|
+
`${field} @@@ '[' || ${param1} || ' TO ' || ${param2} || ']'`,
|
|
196
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
197
|
+
];
|
|
198
|
+
}
|
|
199
|
+
|
|
177
200
|
// Use parameter binding for dates
|
|
178
201
|
const [dateParam, dateState] = nextParam(state);
|
|
179
202
|
return [
|
|
@@ -214,11 +237,36 @@ const rangeToSql = (
|
|
|
214
237
|
];
|
|
215
238
|
} else {
|
|
216
239
|
const [paramName, newState] = nextParam(state);
|
|
240
|
+
let val = value;
|
|
241
|
+
|
|
242
|
+
// Handle date shorthand formats in comparison operators
|
|
243
|
+
if (isDateField) {
|
|
244
|
+
// Year format (YYYY)
|
|
245
|
+
if (/^\d{4}$/.test(value)) {
|
|
246
|
+
if (operator === ">" || operator === ">=") {
|
|
247
|
+
val = `${value}-01-01`;
|
|
248
|
+
} else if (operator === "<" || operator === "<=") {
|
|
249
|
+
val = `${value}-12-31`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Month format (YYYY-MM)
|
|
253
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
254
|
+
const [year, month] = value.split('-');
|
|
255
|
+
if (operator === ">" || operator === ">=") {
|
|
256
|
+
val = `${year}-${month}-01`;
|
|
257
|
+
} else if (operator === "<" || operator === "<=") {
|
|
258
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
259
|
+
val = `${year}-${month}-${lastDay}`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
val = value; // Keep as string, will be converted to number later
|
|
264
|
+
}
|
|
265
|
+
|
|
217
266
|
const rangeOp = operator.replace(">=", ">=").replace("<=", "<=");
|
|
218
|
-
const val = isDateField ? value : Number(value);
|
|
219
267
|
return [
|
|
220
268
|
`${field} @@@ '${rangeOp}' || ${paramName}`,
|
|
221
|
-
addValue(newState, val),
|
|
269
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
222
270
|
];
|
|
223
271
|
}
|
|
224
272
|
};
|
|
@@ -320,6 +320,36 @@ describe("Search Query to SQL Converter", () => {
|
|
|
320
320
|
);
|
|
321
321
|
testIlikeConversion("amount:<-10", "amount < $1", [-10]);
|
|
322
322
|
});
|
|
323
|
+
|
|
324
|
+
test("handles date year shorthand format", () => {
|
|
325
|
+
testIlikeConversion(
|
|
326
|
+
"date:2024",
|
|
327
|
+
"date BETWEEN $1 AND $2",
|
|
328
|
+
["2024-01-01", "2024-12-31"]
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("handles date month shorthand format", () => {
|
|
333
|
+
testIlikeConversion(
|
|
334
|
+
"date:2024-02",
|
|
335
|
+
"date BETWEEN $1 AND $2",
|
|
336
|
+
["2024-02-01", "2024-02-29"] // 2024 is a leap year
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
testIlikeConversion(
|
|
340
|
+
"date:2023-04",
|
|
341
|
+
"date BETWEEN $1 AND $2",
|
|
342
|
+
["2023-04-01", "2023-04-30"]
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("handles date shorthand formats with comparison operators", () => {
|
|
347
|
+
testIlikeConversion(
|
|
348
|
+
"date:>=2024 AND date:<2025",
|
|
349
|
+
"(date >= $1 AND date < $2)",
|
|
350
|
+
["2024-01-01", "2025-12-31"]
|
|
351
|
+
);
|
|
352
|
+
});
|
|
323
353
|
});
|
|
324
354
|
|
|
325
355
|
describe("tsvector Search Type", () => {
|
|
@@ -373,6 +403,36 @@ describe("Search Query to SQL Converter", () => {
|
|
|
373
403
|
["boots", "winter gear"]
|
|
374
404
|
);
|
|
375
405
|
});
|
|
406
|
+
|
|
407
|
+
test("handles date year shorthand format with tsvector", () => {
|
|
408
|
+
testTsvectorConversion(
|
|
409
|
+
"date:2024",
|
|
410
|
+
"date BETWEEN $1 AND $2",
|
|
411
|
+
["2024-01-01", "2024-12-31"]
|
|
412
|
+
);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("handles date month shorthand format with tsvector", () => {
|
|
416
|
+
testTsvectorConversion(
|
|
417
|
+
"date:2024-02",
|
|
418
|
+
"date BETWEEN $1 AND $2",
|
|
419
|
+
["2024-02-01", "2024-02-29"] // 2024 is a leap year
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
testTsvectorConversion(
|
|
423
|
+
"date:2023-04",
|
|
424
|
+
"date BETWEEN $1 AND $2",
|
|
425
|
+
["2023-04-01", "2023-04-30"]
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("handles date shorthand formats with comparison operators with tsvector", () => {
|
|
430
|
+
testTsvectorConversion(
|
|
431
|
+
"date:>=2024 AND date:<2025",
|
|
432
|
+
"(date >= $1 AND date < $2)",
|
|
433
|
+
["2024-01-01", "2025-12-31"]
|
|
434
|
+
);
|
|
435
|
+
});
|
|
376
436
|
});
|
|
377
437
|
|
|
378
438
|
describe("paradedb Search Type", () => {
|
|
@@ -137,6 +137,29 @@ const fieldValueToSql = (
|
|
|
137
137
|
// Rest of the function remains the same...
|
|
138
138
|
switch (schema?.type) {
|
|
139
139
|
case "date":
|
|
140
|
+
// Handle year format (YYYY)
|
|
141
|
+
if (/^\d{4}$/.test(cleanedValue)) {
|
|
142
|
+
const year = cleanedValue;
|
|
143
|
+
const [param1, state1] = nextParam(state);
|
|
144
|
+
const [param2, state2] = nextParam(state1);
|
|
145
|
+
return [
|
|
146
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
147
|
+
addValue(addValue(state2, `${year}-01-01`), `${year}-12-31`),
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Handle year-month format (YYYY-MM)
|
|
152
|
+
if (/^\d{4}-\d{2}$/.test(cleanedValue)) {
|
|
153
|
+
const [year, month] = cleanedValue.split('-');
|
|
154
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
155
|
+
const [param1, state1] = nextParam(state);
|
|
156
|
+
const [param2, state2] = nextParam(state1);
|
|
157
|
+
return [
|
|
158
|
+
`${field} BETWEEN ${param1} AND ${param2}`,
|
|
159
|
+
addValue(addValue(state2, `${year}-${month}-01`), `${year}-${month}-${lastDay}`),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
140
163
|
return [
|
|
141
164
|
`${field} = ${paramName}`,
|
|
142
165
|
addValue(newState, cleanedValue),
|
|
@@ -182,10 +205,35 @@ const rangeToSql = (
|
|
|
182
205
|
}
|
|
183
206
|
|
|
184
207
|
const [paramName, newState] = nextParam(state);
|
|
185
|
-
|
|
208
|
+
let val = value;
|
|
209
|
+
|
|
210
|
+
// Handle date shorthand formats in comparison operators
|
|
211
|
+
if (isDateField) {
|
|
212
|
+
// Year format (YYYY)
|
|
213
|
+
if (/^\d{4}$/.test(value)) {
|
|
214
|
+
if (operator === ">" || operator === ">=") {
|
|
215
|
+
val = `${value}-01-01`;
|
|
216
|
+
} else if (operator === "<" || operator === "<=") {
|
|
217
|
+
val = `${value}-12-31`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Month format (YYYY-MM)
|
|
221
|
+
else if (/^\d{4}-\d{2}$/.test(value)) {
|
|
222
|
+
const [year, month] = value.split('-');
|
|
223
|
+
if (operator === ">" || operator === ">=") {
|
|
224
|
+
val = `${year}-${month}-01`;
|
|
225
|
+
} else if (operator === "<" || operator === "<=") {
|
|
226
|
+
const lastDay = new Date(Number(year), Number(month), 0).getDate();
|
|
227
|
+
val = `${year}-${month}-${lastDay}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
val = value; // Keep as string, will be converted to number later
|
|
232
|
+
}
|
|
233
|
+
|
|
186
234
|
return [
|
|
187
235
|
`${field} ${operator} ${paramName}`,
|
|
188
|
-
addValue(newState, val),
|
|
236
|
+
addValue(newState, isDateField ? val : Number(val)),
|
|
189
237
|
];
|
|
190
238
|
};
|
|
191
239
|
|
|
@@ -254,6 +254,20 @@ const validateFieldValue = (
|
|
|
254
254
|
if (schema.type === "date") {
|
|
255
255
|
const dateValidator = (dateStr: string) => {
|
|
256
256
|
if (!dateStr) return true;
|
|
257
|
+
|
|
258
|
+
// Check for year format (YYYY)
|
|
259
|
+
if (/^\d{4}$/.test(dateStr)) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check for year-month format (YYYY-MM)
|
|
264
|
+
if (/^\d{4}-\d{2}$/.test(dateStr)) {
|
|
265
|
+
const [year, month] = dateStr.split('-').map(Number);
|
|
266
|
+
// Validate month is between 1-12
|
|
267
|
+
return month >= 1 && month <= 12;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check for full date format (YYYY-MM-DD)
|
|
257
271
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
|
258
272
|
return false;
|
|
259
273
|
}
|
|
@@ -264,6 +278,7 @@ const validateFieldValue = (
|
|
|
264
278
|
);
|
|
265
279
|
};
|
|
266
280
|
|
|
281
|
+
// Handle date range with shorthand formats
|
|
267
282
|
if (value.includes("..")) {
|
|
268
283
|
const [start, end] = value.split("..");
|
|
269
284
|
if (!dateValidator(start) || !dateValidator(end)) {
|