Files
openobserve/web/src/composables/useQuery.ts
T
Bhargav e8a514fd24 fix: issue 7640 match_all operator getting replaced (#7653)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Replace regex-based operator spacing with util

- Add state-based `addSpacesToOperators` function

- Update composables and component imports

- Prevent catastrophic regex backtracking


___

### Diagram Walkthrough


```mermaid
flowchart LR
  A["Raw whereClause"] -- "addSpacesToOperators" --> B["Spaced whereClause"]
  B -- "used in" --> C["IndexList.vue / useLogs / useQuery"]
```



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>IndexList.vue</strong><dd><code>Replace regex spacing
with util call</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

web/src/plugins/logs/IndexList.vue

<ul><li>Imported <code>addSpacesToOperators</code> util<br> <li> Removed
inline regex-based spacing<br> <li> Replaced with
<code>addSpacesToOperators(whereClause)</code></ul>


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7653/files#diff-08f058f883b659e713e606991a7ea37ae1ff016b7cccef970489ab5840dc3d09">+2/-10</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useLogs.ts</strong><dd><code>Use util for query
operator spacing</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

web/src/composables/useLogs.ts

<ul><li>Added <code>addSpacesToOperators</code> import<br> <li> Removed
chained <code>.replace</code> regex calls<br> <li> Applied
<code>addSpacesToOperators</code> for whereClause</ul>


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7653/files#diff-1040f61354f92711e0e77ed4e9bfa15586d45a94c7e2ab06cc37372068030e7b">+4/-11</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>useQuery.ts</strong><dd><code>Replace regex in useQuery
with util</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

web/src/composables/useQuery.ts

<ul><li>Imported <code>addSpacesToOperators</code><br> <li> Removed
manual regex replacements<br> <li> Called
<code>addSpacesToOperators(whereClause)</code></ul>


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7653/files#diff-fe0bf52b216893c6a31f2923aab979e0d817c742b0cfd75e524e60db75b1d3e8">+4/-11</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>zincutils.ts</strong><dd><code>Implement
addSpacesToOperators utility</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

web/src/utils/zincutils.ts

<ul><li>Added <code>addSpacesToOperators</code> function<br> <li>
Implements state-based spacing logic<br> <li> Handles quotes,
parentheses, two-char operators<br> <li> Avoids exponential regex
backtracking</ul>


</details>


  </td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7653/files#diff-d3d59688cccdcc6c84ebe88b698c6910030abecc3125f4c0ef81b0df023699ba">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

</details>

___
2025-07-23 17:32:07 +05:30

204 lines
5.9 KiB
TypeScript

import { useStore } from "vuex";
import useNotifications from "@/composables/useNotifications";
import { b64EncodeUnicode, addSpacesToOperators } from "@/utils/zincutils";
import { onBeforeMount, onBeforeUnmount } from "vue";
interface BuildQueryPayload {
from?: number;
size?: number;
timestamp_column?: string;
timestamps?: {
startTime: number | "Invalid Date";
endTime: number | "Invalid Date";
};
timeInterval?: string;
sqlMode: boolean;
currentPage?: number;
selectedStream?: string;
parsedQuery?: {
queryFunctions: string;
whereClause: string;
limit: number;
query: string;
offset: number;
};
streamName: string;
}
const getTimeInterval = (start_time: number, end_time: number) => {
const resPayload = {
interval: "10 second",
keyFormat: "HH:mm:ss",
};
if (end_time - start_time >= 1000000 * 60 * 30) {
resPayload.interval = "15 second";
resPayload.keyFormat = "HH:mm:ss";
}
if (end_time - start_time >= 1000000 * 60 * 60) {
resPayload.interval = "30 second";
resPayload.keyFormat = "HH:mm:ss";
}
if (end_time - start_time >= 1000000 * 3600 * 2) {
resPayload.interval = "1 minute";
resPayload.keyFormat = "MM-DD HH:mm";
}
if (end_time - start_time >= 1000000 * 3600 * 6) {
resPayload.interval = "5 minute";
resPayload.keyFormat = "MM-DD HH:mm";
}
if (end_time - start_time >= 1000000 * 3600 * 24) {
resPayload.interval = "30 minute";
resPayload.keyFormat = "MM-DD HH:mm";
}
if (end_time - start_time >= 1000000 * 86400 * 7) {
resPayload.interval = "1 hour";
resPayload.keyFormat = "MM-DD HH:mm";
}
if (end_time - start_time >= 1000000 * 86400 * 30) {
resPayload.interval = "1 day";
resPayload.keyFormat = "YYYY-MM-DD";
}
return resPayload;
};
const useQuery = () => {
let parser: any;
const store = useStore();
const { showErrorNotification } = useNotifications();
onBeforeMount(async () => {
await importSqlParser();
});
onBeforeUnmount(() => {
parser = null;
});
const importSqlParser = async () => {
const useSqlParser: any = await import("@/composables/useParser");
const { sqlParser }: any = useSqlParser.default();
parser = await sqlParser();
};
const parseQuery = (query: string, sqlMode = false) => {
const parsedParams = {
queryFunctions: "",
whereClause: "",
limit: null,
query: query,
offset: null,
};
if (sqlMode == true) {
const parsedSQL: any = parser.astify(query);
if (parsedSQL.limit != null) {
parsedParams.limit = parsedSQL.limit.value[0].value;
if (parsedSQL.limit.separator == "offset") {
parsedParams.offset = parsedSQL.limit.value[1].value || 0;
}
parsedSQL.limit = null;
parsedParams.query = parser.sqlify(parsedSQL);
//replace backticks with \" for sql_mode
parsedParams.query = parsedParams.query.replace(/`/g, '"');
}
} else {
const parseQuery = parsedParams.query.split("|");
let queryFunctions = "";
let whereClause = "";
if (parseQuery.length > 1) {
queryFunctions = "," + parseQuery[0].trim();
whereClause = parseQuery[1].trim();
} else {
whereClause = parseQuery[0].trim();
}
if (whereClause.trim() != "") {
// More efficient approach: replace operators only when not inside quotes or parentheses
// This avoids catastrophic backtracking by using a simpler state-based approach
whereClause = addSpacesToOperators(whereClause);
// const parsedSQL = whereClause.split(" ");
// searchObj.data.stream.selectedStreamFields.forEach((field: any) => {
// parsedSQL.forEach((node: any, index: any) => {
// if (node == field.name) {
// node = node.replaceAll('"', "");
// parsedSQL[index] = '"' + node + '"';
// }
// });
// });
// whereClause = parsedSQL.join(" ");
parsedParams.whereClause = whereClause;
}
parsedParams.queryFunctions = queryFunctions;
}
};
const buildQueryPayload = (data: BuildQueryPayload) => {
try {
const req: any = {
query: {
sql: 'select *[QUERY_FUNCTIONS] from "[INDEX_NAME]" [WHERE_CLAUSE]',
start_time: (new Date().getTime() - 900000) * 1000,
end_time: new Date().getTime() * 1000,
from: data.from || 0,
size: data.size || 100,
},
aggs: {
histogram:
"select histogram(" +
(data?.timestamp_column ||
store.state.zoConfig.timestamp_column ||
"_timestamp") +
", '[INTERVAL]') AS zo_sql_key, count(*) AS zo_sql_num from query GROUP BY zo_sql_key ORDER BY zo_sql_key",
},
};
req.aggs.histogram = req.aggs.histogram.replaceAll(
"[INTERVAL]",
data.timeInterval,
);
req.query.sql = req.query.sql.replaceAll(
"[QUERY_FUNCTIONS]",
data.parsedQuery?.queryFunctions || "",
);
req.query.sql = req.query.sql.replaceAll("[INDEX_NAME]", data.streamName);
req.query.sql = req.query.sql.replaceAll(
"[WHERE_CLAUSE]",
data.parsedQuery?.whereClause || "",
);
if (data?.timestamps?.startTime)
req.query.start_time = data.timestamps.startTime;
if (data?.timestamps?.endTime)
req.query.end_time = data.timestamps.endTime;
if (store.state.zoConfig.sql_base64_enabled) {
req["encoding"] = "base64";
req.query.sql = b64EncodeUnicode(req.query.sql);
if (!data.sqlMode && data.currentPage == 0) {
req.aggs.histogram = b64EncodeUnicode(req.aggs.histogram);
}
}
return req;
} catch (e: any) {
showErrorNotification("Invalid SQL Syntax");
}
};
return { getTimeInterval, parseQuery, buildQueryPayload };
};
export default useQuery;