Copilot commented on code in PR #1209:
URL: 
https://github.com/apache/skywalking-banyandb/pull/1209#discussion_r3534882424


##########
ui/src/components/BydbQL/Index.vue:
##########
@@ -584,6 +658,26 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
           @ready="onCodeMirrorReady"
         />
       </div>
+      <div class="query-params-container">
+        <div class="params-header">
+          <span class="params-title">Parameters (bound to ? placeholders in 
order)</span>
+          <el-button size="small" @click="addParam" :disabled="loading"> Add 
Parameter </el-button>
+        </div>
+        <div v-for="(param, index) in queryParams" :key="index" 
class="param-row">
+          <span class="param-index">?{{ index + 1 }}</span>
+          <el-select v-model="param.type" size="small" class="param-type">

Review Comment:
   Using the array index as the v-for key can cause row state to “shift” when 
removing parameters (inputs/selects may appear to change the wrong row). Use a 
stable per-parameter id (e.g., store an `id` on each param when added) and key 
by that id instead of `index`.



##########
docs/interacting/bydbql.md:
##########
@@ -154,6 +157,76 @@ TIME < '-1d'
 
 The `parseTime` function automatically determines whether a timestamp is 
absolute (RFC3339) or relative (duration string) and converts it appropriately.
 
+## 2.6. Parameter Binding
+
+BydbQL supports positional parameter binding with the `?` placeholder, so 
untrusted values never need to be concatenated into the query string. This 
eliminates QL injection: a bound value is always treated as a plain value and 
can never alter the query structure.
+
+```sql
+SELECT trace_id, message
+FROM STREAM logs IN group1
+TIME > ?
+WHERE message MATCH(?)
+```
+
+Parameters are supplied alongside the query and are bound to the placeholders 
in their order of appearance. Each parameter is a `model.v1.TagValue`. Over 
HTTP:
+
+```json
+POST /api/v1/bydbql/query
+{
+  "query": "SELECT trace_id, message FROM STREAM logs IN group1 TIME > ? WHERE 
message MATCH(?)",
+  "params": [
+    {"str": {"value": "-30m"}},
+    {"str": {"value": "error"}}
+  ]
+}
+```
+
+### 2.6.1. Supported Positions
+
+Placeholders are accepted in *value* positions only. The complete list, with 
the parameter types each position accepts:
+
+| Position | Example | Accepted parameter types |
+|---|---|---|
+| `TIME` comparison value | `TIME > ?` | `str` (RFC3339, relative like `-30m`, 
or `now`), `timestamp` |

Review Comment:
   The supported-positions table says TIME placeholders accept only 
`str`/`timestamp`, but later in this same section you note that `int` 
parameters bind in TIME positions (and binder.go also accepts `TagValue_Int`). 
This is internally inconsistent and may mislead users; either include `int` in 
the table (with the existing caveat) or change binding rules to reject it.



##########
mcp/src/query/validation.ts:
##########
@@ -119,12 +133,77 @@ export function normalizeQueryHints(args: unknown): 
QueryHints {
   return {
     description: typeof rawArgs.description === 'string' ? 
rawArgs.description.trim() : undefined,
     BydbQL: typeof rawArgs.BydbQL === 'string' ? rawArgs.BydbQL.trim() : 
undefined,
+    params: Array.isArray(rawArgs.params) ? (rawArgs.params as BydbQLParam[]) 
: undefined,
     resource_type: typeof rawArgs.resource_type === 'string' ? 
rawArgs.resource_type.trim() : undefined,
     resource_name: typeof rawArgs.resource_name === 'string' ? 
rawArgs.resource_name.trim() : undefined,
     group: typeof rawArgs.group === 'string' ? rawArgs.group.trim() : 
undefined,
   };
 }
 
+function validateParamString(value: unknown, position: number): string {
+  if (typeof value !== 'string') {
+    throw new Error(`params[${position}]: str value must be a string`);
+  }
+  if (value.length > maxParamValueLength) {
+    throw new Error(`params[${position}]: value exceeds ${maxParamValueLength} 
characters`);
+  }
+  return value;
+}
+
+function validateParamInteger(value: unknown, position: number): string {
+  if (typeof value === 'number' && Number.isSafeInteger(value)) {
+    return String(value);
+  }
+  if (typeof value === 'string' && integerPattern.test(value)) {
+    return value;
+  }
+  throw new Error(`params[${position}]: int value must be an integer`);
+}

Review Comment:
   validateParamInteger accepts arbitrarily long integer strings (it only 
regex-checks), even though maxParamValueLength exists. This can allow very 
large param payloads (e.g., megabytes of digits) to get passed through to the 
HTTP API, which is unnecessary and can become a resource/DoS vector. Apply the 
same length limit used for string params after converting the int to its string 
representation.



##########
docs/api-reference.md:
##########
@@ -2117,6 +2117,7 @@ QueryRequest is the main request message for BydbQL 
queries
 | Field | Type | Label | Description |
 | ----- | ---- | ----- | ----------- |
 | query | [string](#string) |  | query is the BydbQL query string |
+| params | [banyandb.model.v1.TagValue](#banyandb-model-v1-TagValue) | 
repeated | params are bound to the `?` placeholders in the query string by 
their order of appearance. The number of params must match the number of 
placeholders. Accepted variants depend on the placeholder&#39;s position: 
str/timestamp for TIME values, str/int/null for scalar comparisons, 
str/int/null/str_array/int_array for IN/MATCH/HAVING value lists (arrays expand 
in place), and int for LIMIT/OFFSET/TOP N counts. binary_data is rejected 
everywhere. |

Review Comment:
   This API reference text says TIME placeholders accept only str/timestamp, 
but the binder accepts int parameters in TIME positions as well. Keep this 
description consistent with the actual binding rules (or update the binding 
rules to match the docs).



##########
api/proto/banyandb/bydbql/v1/query.proto:
##########
@@ -33,6 +34,13 @@ option java_package = 
"org.apache.skywalking.banyandb.bydbql.v1";
 message QueryRequest {
   // query is the BydbQL query string
   string query = 1 [(validate.rules).string.min_len = 1];
+  // params are bound to the `?` placeholders in the query string by their 
order of appearance.
+  // The number of params must match the number of placeholders.
+  // Accepted variants depend on the placeholder's position: str/timestamp for 
TIME values,
+  // str/int/null for scalar comparisons, str/int/null/str_array/int_array for 
IN/MATCH/HAVING
+  // value lists (arrays expand in place), and int for LIMIT/OFFSET/TOP N 
counts.

Review Comment:
   The QueryRequest `params` comment says TIME placeholders accept only 
str/timestamp, but the binder accepts int parameters in TIME positions 
(BindParams.bindTimeValue handles TagValue_Int) and the docs mention that int 
binds but later fails transformation. Please align this proto comment with 
actual accepted variants (or adjust binder semantics accordingly).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to