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


##########
ui/src/components/BydbQL/Index.vue:
##########
@@ -273,19 +274,79 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
     error.value = null;
     executionTime.value = 0;
   }
-  // Setup keyboard shortcuts for CodeMirror
-  onMounted(() => {
-    nextTick(() => {
-      // Find the CodeMirror instance and add keyboard shortcuts
-      const codeMirrorElement = document.querySelector('.query-input 
.CodeMirror');
-      if (codeMirrorElement && codeMirrorElement.CodeMirror) {
-        const cm = codeMirrorElement.CodeMirror;
-        cm.setOption('extraKeys', {
-          'Ctrl-Enter': executeQuery,
-          'Cmd-Enter': executeQuery,
-        });
-      }
+
+  function onCodeMirrorReady(cm) {
+    codeMirrorInstance.value = cm;
+    cm.setOption('extraKeys', {
+      'Ctrl-Enter': executeQuery,
+      'Cmd-Enter': executeQuery,
+      'Ctrl-Space': 'autocomplete',
     });
+  }
+
+  // Fetch groups and schemas for autocomplete
+  async function fetchSchemaData() {
+    try {
+      const groupResponse = await getGroupList();
+      if (groupResponse.error) {
+        console.error('Failed to fetch groups:', groupResponse.error);
+        return;
+      }
+
+      // Only include groups with valid catalog types (property, stream, 
trace, measure, topn)
+      const groups = (groupResponse.group || [])
+        .filter((g) => CatalogToGroupType[g.catalog])
+        .map((g) => g.metadata.name);
+      const schemas = {
+        stream: [],
+        measure: [],
+        trace: [],
+        property: [],
+        topn: [],
+      };
+
+      // Fetch schemas for each type
+      for (const group of groupResponse.group || []) {
+        const groupName = group.metadata.name;
+        const catalog = group.catalog;
+        const type = CatalogToGroupType[catalog];
+
+        if (type) {
+          try {
+            const schemaResponse = await getAllTypesOfResourceList(type, 
groupName);
+            if (!schemaResponse.error) {
+              const schemaList = schemaResponse[type === 'property' ? 
'properties' : type] || [];
+              const schemaNames = schemaList.map((s) => 
s.metadata?.name).filter(Boolean);
+              schemas[type] = [...new Set([...schemas[type], ...schemaNames])];
+            }
+          } catch (e) {
+            console.error(`Failed to fetch ${type} schemas for group 
${groupName}:`, e);
+          }
+
+          // TopN resources are in CATALOG_MEASURE groups
+          if (catalog === 'CATALOG_MEASURE') {
+            try {
+              const topnResponse = await getTopNAggregationList(groupName);
+              if (!topnResponse.error) {
+                const topnList = topnResponse.topNAggregation || [];
+                const topnNames = topnList.map((s) => 
s.metadata?.name).filter(Boolean);
+                schemas.topn = [...new Set([...schemas.topn, ...topnNames])];
+              }
+            } catch (e) {
+              console.error(`Failed to fetch topn schemas for group 
${groupName}:`, e);
+            }
+          }
+        }
+      }

Review Comment:
   The schema fetching logic loops through all groups and makes sequential API 
calls for each group (lines 309-340). This can result in a significant number 
of sequential HTTP requests, which may cause slow initial load times. Consider 
batching these requests or using Promise.all() to fetch schemas for multiple 
groups in parallel.



##########
ui/src/components/CodeMirror/bydbql-hint.js:
##########
@@ -0,0 +1,241 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import CodeMirror from 'codemirror';
+
+// BydbQL keywords
+const BYDBQL_KEYWORDS = [
+  'SELECT',
+  'FROM',
+  'WHERE',
+  'ORDER BY',
+  'LIMIT',
+  'OFFSET',
+  'AND',
+  'OR',
+  'NOT',
+  'IN',
+  'LIKE',
+  'BETWEEN',
+  'IS',
+  'NULL',
+  'TRUE',
+  'FALSE',
+  'AS',
+  'DISTINCT',
+  'GROUP BY',
+  'HAVING',
+  'TIME',
+  'ASC',
+  'DESC',
+  'SHOW',
+  'TOP',
+  'UNION',
+  'INTERSECT',
+  'EXCEPT',
+  'AGGREGATE BY',
+  'WITH QUERY_TRACE',
+  'LIMIT',
+  'OFFSET',
+  'ORDER BY',
+  'ON',
+  'STAGES',
+  'WHERE',
+  'GROUP',
+  'BY',
+  'IN',
+  'NOT IN',
+  'HAVING',
+  'MATCH',
+  'SUM',
+  'MEAN',
+  'AVG',
+  'COUNT',
+  'MAX',
+  'MIN',
+  'TAG',
+];
+
+// BydbQL entity types
+const ENTITY_TYPES = ['STREAM', 'MEASURE', 'TRACE', 'PROPERTY', 'TOPN'];
+let schemasAndGroups = {
+  groups: [],
+  schemas: {},
+};
+
+export function updateSchemasAndGroups(groups, schemas) {
+  schemasAndGroups.groups = groups || [];
+  schemasAndGroups.schemas = schemas || {};
+}
+
+function getWordAt(cm, pos) {
+  const line = cm.getLine(pos.line);
+  const start = pos.ch;
+  const end = pos.ch;
+
+  let wordStart = start;
+  let wordEnd = end;
+

Review Comment:
   The variables `start` and `end` are initialized with `pos.ch` but are never 
used. Only `wordStart` and `wordEnd` are actually utilized in the function. 
Consider removing these unused variable declarations.
   ```suggestion
   
   
   
     let wordStart = pos.ch;
     let wordEnd = pos.ch;
   ```



##########
ui/src/components/CodeMirror/index.vue:
##########
@@ -65,61 +67,127 @@
         type: Boolean,
         default: true,
       },
+      enableHint: {
+        type: Boolean,
+        default: false,
+      },
+      extraKeys: {
+        type: Object,
+        default: () => ({}),
+      },
     },
-    emits: ['update:modelValue'],
+    emits: ['update:modelValue', 'ready'],
     setup(props, { emit }) {
       const textarea = ref(null);
       const code = ref(props.modelValue);
       let coder;
       watch(
         () => props.modelValue,
         (val) => {
-          coder?.setValue(val);
+          const currentValue = coder?.getValue();
+          if (val !== currentValue) {
+            coder?.setValue(val);
+          }
         },
       );
+
+      // Get mode based on prop
+      const getModeString = () => {
+        if (props.mode === 'bydbql') {
+          return 'text/x-bydbql';
+        }
+        if (props.mode === 'yaml') {
+          return 'text/x-yaml';
+        }
+        if (props.mode === 'css') {
+          return 'text/css';
+        }
+        return 'text/x-yaml';
+      };
+
       const options = {
-        mode: 'text/x-yaml',
+        mode: getModeString(),
         tabSize: 2,
         theme: props.theme,
         lineNumbers: true,
         line: true,
         readOnly: props.readonly,
         lint: props.lint,
-        gutters: ['CodeMirror-lint-markers'],
+        gutters: props.lint ? ['CodeMirror-lint-markers'] : [],
         styleActiveLine: props.styleActiveLine,
         autoRefresh: props.autoRefresh,
         height: '500px',
+        extraKeys: props.extraKeys,
       };
+
       const initialize = async () => {
         try {
-          /*  let theme = `codemirror/theme/${props.theme}.css`
-        await import(theme) */
           if (props.lint) {
             await import('codemirror/addon/lint/lint.js');
             await import('codemirror/addon/lint/lint.css');
           }
-          /* if (props.mode) {
-          await import(`codemirror/mode/${props.mode}/${props.mode}.js`)
-        } */
           if (props.autoRefresh) {
             await import('codemirror/addon/display/autorefresh');
           }
           if (props.styleActiveLine) {
             await import('codemirror/addon/selection/active-line');
           }
-        } catch (e) {}
+          if (props.enableHint) {
+            await import('codemirror/addon/hint/show-hint.js');
+            await import('codemirror/addon/hint/show-hint.css');
+          }
+        } catch (e) {
+          console.error('Error loading CodeMirror addons:', e);
+        }
+
         coder = CodeMirror.fromTextArea(textarea.value, options);
+
         coder.on('blur', (coder) => {
           const newValue = coder.getValue();
           emit('update:modelValue', newValue);
         });
+
+        // Enable autocomplete on Ctrl+Space or when typing
+        if (props.enableHint) {
+          coder.on('keyup', (cm, event) => {
+            // Don't show hints for special keys
+            const excludedKeys = [
+              8, // Backspace
+              9, // Tab
+              13, // Enter
+              16,
+              17,
+              18, // Shift, Ctrl, Alt
+              20, // Caps Lock
+              27, // Escape
+              33,
+              34,
+              35,
+              36,
+              37,
+              38,
+              39,
+              40, // Page/Arrow keys
+            ];
+
+            if (!cm.state.completionActive && 
!excludedKeys.includes(event.keyCode)) {
+              CodeMirror.commands.autocomplete(cm, null, { completeSingle: 
false });

Review Comment:
   The `CodeMirror.commands.autocomplete` is called without specifying a hint 
function or mode. CodeMirror will attempt to automatically determine which hint 
helper to use based on the editor's mode. However, this might fail if the 
mode-to-hint mapping isn't properly established. Consider explicitly passing 
the hint helper: `CodeMirror.commands.autocomplete(cm, CodeMirror.hint.bydbql, 
{ completeSingle: false })`.



##########
ui/src/components/BydbQL/Index.vue:
##########
@@ -273,19 +274,79 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
     error.value = null;
     executionTime.value = 0;
   }
-  // Setup keyboard shortcuts for CodeMirror
-  onMounted(() => {
-    nextTick(() => {
-      // Find the CodeMirror instance and add keyboard shortcuts
-      const codeMirrorElement = document.querySelector('.query-input 
.CodeMirror');
-      if (codeMirrorElement && codeMirrorElement.CodeMirror) {
-        const cm = codeMirrorElement.CodeMirror;
-        cm.setOption('extraKeys', {
-          'Ctrl-Enter': executeQuery,
-          'Cmd-Enter': executeQuery,
-        });
-      }
+
+  function onCodeMirrorReady(cm) {
+    codeMirrorInstance.value = cm;
+    cm.setOption('extraKeys', {
+      'Ctrl-Enter': executeQuery,
+      'Cmd-Enter': executeQuery,
+      'Ctrl-Space': 'autocomplete',
     });

Review Comment:
   The `extraKeys` prop is passed during CodeMirror initialization (line 120), 
but then overridden using `setOption` in the `onCodeMirrorReady` callback 
(lines 280-284). This means any extraKeys passed via props will be completely 
replaced rather than merged. If the parent component passes extraKeys through 
props, they will be lost. Consider merging the prop extraKeys with the new keys 
instead of replacing them entirely.



##########
ui/src/components/CodeMirror/index.vue:
##########
@@ -65,61 +67,127 @@
         type: Boolean,
         default: true,
       },
+      enableHint: {
+        type: Boolean,
+        default: false,
+      },
+      extraKeys: {
+        type: Object,
+        default: () => ({}),
+      },
     },
-    emits: ['update:modelValue'],
+    emits: ['update:modelValue', 'ready'],
     setup(props, { emit }) {
       const textarea = ref(null);
       const code = ref(props.modelValue);
       let coder;
       watch(
         () => props.modelValue,
         (val) => {
-          coder?.setValue(val);
+          const currentValue = coder?.getValue();
+          if (val !== currentValue) {
+            coder?.setValue(val);
+          }
         },
       );
+
+      // Get mode based on prop
+      const getModeString = () => {
+        if (props.mode === 'bydbql') {
+          return 'text/x-bydbql';
+        }
+        if (props.mode === 'yaml') {
+          return 'text/x-yaml';
+        }
+        if (props.mode === 'css') {
+          return 'text/css';
+        }
+        return 'text/x-yaml';
+      };
+
       const options = {
-        mode: 'text/x-yaml',
+        mode: getModeString(),
         tabSize: 2,
         theme: props.theme,
         lineNumbers: true,
         line: true,
         readOnly: props.readonly,
         lint: props.lint,
-        gutters: ['CodeMirror-lint-markers'],
+        gutters: props.lint ? ['CodeMirror-lint-markers'] : [],
         styleActiveLine: props.styleActiveLine,
         autoRefresh: props.autoRefresh,
         height: '500px',
+        extraKeys: props.extraKeys,
       };
+
       const initialize = async () => {
         try {
-          /*  let theme = `codemirror/theme/${props.theme}.css`
-        await import(theme) */
           if (props.lint) {
             await import('codemirror/addon/lint/lint.js');
             await import('codemirror/addon/lint/lint.css');
           }
-          /* if (props.mode) {
-          await import(`codemirror/mode/${props.mode}/${props.mode}.js`)
-        } */
           if (props.autoRefresh) {
             await import('codemirror/addon/display/autorefresh');
           }
           if (props.styleActiveLine) {
             await import('codemirror/addon/selection/active-line');
           }
-        } catch (e) {}
+          if (props.enableHint) {
+            await import('codemirror/addon/hint/show-hint.js');
+            await import('codemirror/addon/hint/show-hint.css');
+          }
+        } catch (e) {
+          console.error('Error loading CodeMirror addons:', e);
+        }

Review Comment:
   The CodeMirror initialization continues even if addon imports fail. If the 
hint addon fails to load but `props.enableHint` is true, the subsequent 
hint-related code (lines 151-177) will fail when attempting to use 
`CodeMirror.commands.autocomplete`. Consider checking whether the hint addon 
loaded successfully before setting up the autocomplete functionality, or 
re-throw critical errors.



##########
ui/src/components/CodeMirror/bydbql-hint.js:
##########
@@ -0,0 +1,241 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import CodeMirror from 'codemirror';
+
+// BydbQL keywords
+const BYDBQL_KEYWORDS = [
+  'SELECT',
+  'FROM',
+  'WHERE',
+  'ORDER BY',
+  'LIMIT',
+  'OFFSET',
+  'AND',
+  'OR',
+  'NOT',
+  'IN',
+  'LIKE',
+  'BETWEEN',
+  'IS',
+  'NULL',
+  'TRUE',
+  'FALSE',
+  'AS',
+  'DISTINCT',
+  'GROUP BY',
+  'HAVING',
+  'TIME',
+  'ASC',
+  'DESC',
+  'SHOW',
+  'TOP',
+  'UNION',
+  'INTERSECT',
+  'EXCEPT',
+  'AGGREGATE BY',
+  'WITH QUERY_TRACE',
+  'LIMIT',
+  'OFFSET',
+  'ORDER BY',
+  'ON',
+  'STAGES',
+  'WHERE',
+  'GROUP',
+  'BY',
+  'IN',
+  'NOT IN',
+  'HAVING',
+  'MATCH',
+  'SUM',
+  'MEAN',
+  'AVG',
+  'COUNT',
+  'MAX',
+  'MIN',
+  'TAG',
+];

Review Comment:
   Duplicate keyword entries detected in the BYDBQL_KEYWORDS array. The 
following keywords appear multiple times:
   - 'LIMIT' (lines 28, 54)
   - 'OFFSET' (lines 29, 55)
   - 'ORDER BY' (lines 27, 56)
   - 'WHERE' (lines 26, 59)
   - 'IN' (lines 33, 62)
   - 'HAVING' (lines 43, 64)
   
   These duplicates are unnecessary and should be removed to maintain code 
clarity.



##########
ui/src/components/BydbQL/Index.vue:
##########
@@ -273,19 +274,79 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
     error.value = null;
     executionTime.value = 0;
   }
-  // Setup keyboard shortcuts for CodeMirror
-  onMounted(() => {
-    nextTick(() => {
-      // Find the CodeMirror instance and add keyboard shortcuts
-      const codeMirrorElement = document.querySelector('.query-input 
.CodeMirror');
-      if (codeMirrorElement && codeMirrorElement.CodeMirror) {
-        const cm = codeMirrorElement.CodeMirror;
-        cm.setOption('extraKeys', {
-          'Ctrl-Enter': executeQuery,
-          'Cmd-Enter': executeQuery,
-        });
-      }
+
+  function onCodeMirrorReady(cm) {
+    codeMirrorInstance.value = cm;
+    cm.setOption('extraKeys', {
+      'Ctrl-Enter': executeQuery,
+      'Cmd-Enter': executeQuery,
+      'Ctrl-Space': 'autocomplete',

Review Comment:
   The 'Ctrl-Space' key binding for 'autocomplete' is redundant. The 
autocomplete is already triggered automatically on keyup (lines 152-176) for 
all non-excluded keys. Additionally, the CodeMirror autocomplete command is 
typically invoked as a function, not as a string. Consider either removing this 
binding or verifying that it's necessary for manual triggering.
   ```suggestion
   
   ```



##########
ui/src/components/CodeMirror/bydbql-hint.js:
##########
@@ -0,0 +1,241 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import CodeMirror from 'codemirror';
+
+// BydbQL keywords
+const BYDBQL_KEYWORDS = [
+  'SELECT',
+  'FROM',
+  'WHERE',
+  'ORDER BY',
+  'LIMIT',
+  'OFFSET',
+  'AND',
+  'OR',
+  'NOT',
+  'IN',
+  'LIKE',
+  'BETWEEN',
+  'IS',
+  'NULL',
+  'TRUE',
+  'FALSE',
+  'AS',
+  'DISTINCT',
+  'GROUP BY',
+  'HAVING',
+  'TIME',
+  'ASC',
+  'DESC',
+  'SHOW',
+  'TOP',
+  'UNION',
+  'INTERSECT',
+  'EXCEPT',
+  'AGGREGATE BY',
+  'WITH QUERY_TRACE',
+  'LIMIT',
+  'OFFSET',
+  'ORDER BY',
+  'ON',
+  'STAGES',
+  'WHERE',
+  'GROUP',
+  'BY',
+  'IN',
+  'NOT IN',
+  'HAVING',
+  'MATCH',
+  'SUM',
+  'MEAN',
+  'AVG',
+  'COUNT',
+  'MAX',
+  'MIN',
+  'TAG',
+];
+
+// BydbQL entity types
+const ENTITY_TYPES = ['STREAM', 'MEASURE', 'TRACE', 'PROPERTY', 'TOPN'];
+let schemasAndGroups = {
+  groups: [],
+  schemas: {},
+};
+
+export function updateSchemasAndGroups(groups, schemas) {
+  schemasAndGroups.groups = groups || [];
+  schemasAndGroups.schemas = schemas || {};
+}
+
+function getWordAt(cm, pos) {
+  const line = cm.getLine(pos.line);
+  const start = pos.ch;
+  const end = pos.ch;
+
+  let wordStart = start;
+  let wordEnd = end;
+
+  // Find word boundaries
+  while (wordStart > 0 && /\w/.test(line.charAt(wordStart - 1))) {
+    wordStart--;
+  }
+  while (wordEnd < line.length && /\w/.test(line.charAt(wordEnd))) {
+    wordEnd++;
+  }
+
+  return {
+    word: line.slice(wordStart, wordEnd),
+    start: wordStart,
+    end: wordEnd,
+  };
+}
+
+// Analyze the query context to determine what to suggest
+function getQueryContext(cm, cursor) {
+  const textBeforeCursor = cm.getRange({ line: 0, ch: 0 }, cursor);
+
+  // Check if we're typing after 'in' (group name context)
+  const inMatch = 
textBeforeCursor.match(/\bFROM\s+(STREAM|MEASURE|TRACE|PROPERTY|TOPN)\s+(\w+)\s+in\s+\w*$/i);
+  if (inMatch) {
+    return { type: 'group', entityType: inMatch[1].toLowerCase() };
+  }
+
+  // Check if we're typing after schema name (expecting 'in')
+  const schemaMatch = 
textBeforeCursor.match(/\bFROM\s+(STREAM|MEASURE|TRACE|PROPERTY|TOPN)\s+(\w+)\s+\w*$/i);
+  if (schemaMatch) {
+    return { type: 'in_keyword' };
+  }
+
+  // Check if we're typing after entity type (schema name context)
+  const entityMatch = 
textBeforeCursor.match(/\bFROM\s+(STREAM|MEASURE|TRACE|PROPERTY|TOPN)\s+\w*$/i);
+  if (entityMatch) {
+    return { type: 'schema', entityType: entityMatch[1].toLowerCase() };
+  }
+
+  // Check if we're typing after FROM (entity type context)
+  if (/\bFROM\s+\w*$/i.test(textBeforeCursor)) {

Review Comment:
   The regex patterns for matching context (lines 115, 121, 127, 133) use 
`\w*$` which will match word characters at the end. However, if the cursor is 
positioned in the middle of a word or immediately after whitespace, these 
patterns may not behave as expected. The pattern assumes the user is always 
typing at the very end of the text, which might not account for multi-line 
queries or editing in the middle of the query.
   ```suggestion
     const inMatch = 
textBeforeCursor.match(/\bFROM\s+(STREAM|MEASURE|TRACE|PROPERTY|TOPN)\s+(\w+)\s+in\s+(\w*)$/i);
     if (inMatch) {
       return { type: 'group', entityType: inMatch[1].toLowerCase() };
     }
   
     // Check if we're typing after schema name (expecting 'in')
     const schemaMatch = 
textBeforeCursor.match(/\bFROM\s+(STREAM|MEASURE|TRACE|PROPERTY|TOPN)\s+(\w+)\s+(\w*)$/i);
     if (schemaMatch) {
       return { type: 'in_keyword' };
     }
   
     // Check if we're typing after entity type (schema name context)
     const entityMatch = 
textBeforeCursor.match(/\bFROM\s+(STREAM|MEASURE|TRACE|PROPERTY|TOPN)\s+(\w*)$/i);
     if (entityMatch) {
       return { type: 'schema', entityType: entityMatch[1].toLowerCase() };
     }
   
     // Check if we're typing after FROM (entity type context)
     if (/\bFROM\s+(\w*)$/i.test(textBeforeCursor)) {
   ```



##########
ui/src/components/BydbQL/Index.vue:
##########
@@ -304,12 +365,14 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
       <div class="query-input-container">
         <CodeMirror
           v-model="queryText"
-          :mode="'sql'"
-          :lint="false"
+          :mode="'bydbql'"
+          :lint="true"

Review Comment:
   Linting is enabled for bydbql mode (line 369), but there's no lint 
implementation for the 'bydbql' mode. The CodeMirror lint addon requires a 
mode-specific linter to be registered. Without a bydbql-specific linter, 
enabling lint will likely have no effect or could cause errors. Consider either 
implementing a bydbql linter or setting `:lint="false"` for bydbql mode.
   ```suggestion
             :lint="false"
   ```



##########
ui/src/components/CodeMirror/bydbql-mode.js:
##########
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import CodeMirror from 'codemirror';
+
+// Define BydbQL mode extending SQL
+CodeMirror.defineMode('bydbql', function (config) {
+  const sqlMode = CodeMirror.getMode(config, 'text/x-sql');
+
+  const entityTypes = {
+    STREAM: true,
+    MEASURE: true,
+    TRACE: true,
+    PROPERTY: true,
+    TOPN: true,
+  };
+
+  // BydbQL-specific keywords
+  const bydbqlKeywords = {
+    SELECT: true,
+    FROM: true,
+    WHERE: true,
+    ORDER: true,
+    BY: true,
+    ASC: true,
+    DESC: true,
+    LIMIT: true,
+    OFFSET: true,
+    AND: true,
+    OR: true,
+    NOT: true,

Review Comment:
   This property is duplicated [in a later property](1).



##########
ui/src/components/CodeMirror/index.vue:
##########
@@ -65,61 +67,127 @@
         type: Boolean,
         default: true,
       },
+      enableHint: {
+        type: Boolean,
+        default: false,
+      },
+      extraKeys: {
+        type: Object,
+        default: () => ({}),
+      },
     },
-    emits: ['update:modelValue'],
+    emits: ['update:modelValue', 'ready'],
     setup(props, { emit }) {
       const textarea = ref(null);
       const code = ref(props.modelValue);
       let coder;
       watch(
         () => props.modelValue,
         (val) => {
-          coder?.setValue(val);
+          const currentValue = coder?.getValue();
+          if (val !== currentValue) {
+            coder?.setValue(val);
+          }
         },
       );
+
+      // Get mode based on prop
+      const getModeString = () => {
+        if (props.mode === 'bydbql') {
+          return 'text/x-bydbql';
+        }
+        if (props.mode === 'yaml') {
+          return 'text/x-yaml';
+        }
+        if (props.mode === 'css') {
+          return 'text/css';
+        }
+        return 'text/x-yaml';
+      };
+
       const options = {
-        mode: 'text/x-yaml',
+        mode: getModeString(),
         tabSize: 2,
         theme: props.theme,
         lineNumbers: true,
         line: true,
         readOnly: props.readonly,
         lint: props.lint,
-        gutters: ['CodeMirror-lint-markers'],
+        gutters: props.lint ? ['CodeMirror-lint-markers'] : [],
         styleActiveLine: props.styleActiveLine,
         autoRefresh: props.autoRefresh,
         height: '500px',
+        extraKeys: props.extraKeys,
       };
+
       const initialize = async () => {
         try {
-          /*  let theme = `codemirror/theme/${props.theme}.css`
-        await import(theme) */
           if (props.lint) {
             await import('codemirror/addon/lint/lint.js');
             await import('codemirror/addon/lint/lint.css');
           }
-          /* if (props.mode) {
-          await import(`codemirror/mode/${props.mode}/${props.mode}.js`)
-        } */
           if (props.autoRefresh) {
             await import('codemirror/addon/display/autorefresh');
           }
           if (props.styleActiveLine) {
             await import('codemirror/addon/selection/active-line');
           }
-        } catch (e) {}
+          if (props.enableHint) {
+            await import('codemirror/addon/hint/show-hint.js');
+            await import('codemirror/addon/hint/show-hint.css');
+          }
+        } catch (e) {
+          console.error('Error loading CodeMirror addons:', e);
+        }
+
         coder = CodeMirror.fromTextArea(textarea.value, options);
+
         coder.on('blur', (coder) => {
           const newValue = coder.getValue();
           emit('update:modelValue', newValue);
         });
+
+        // Enable autocomplete on Ctrl+Space or when typing

Review Comment:
   The comment states "Enable autocomplete on Ctrl+Space or when typing", but 
the Ctrl+Space binding is set separately in the parent component 
(BydbQL/Index.vue line 283). The comment is misleading as this code block only 
handles the "when typing" part. Consider updating the comment to accurately 
reflect that this code handles automatic triggering on keyup events.
   ```suggestion
           // Enable automatic autocomplete when typing (on keyup events)
   ```



##########
ui/src/components/CodeMirror/index.vue:
##########
@@ -65,61 +67,127 @@
         type: Boolean,
         default: true,
       },
+      enableHint: {
+        type: Boolean,
+        default: false,
+      },
+      extraKeys: {
+        type: Object,
+        default: () => ({}),
+      },
     },
-    emits: ['update:modelValue'],
+    emits: ['update:modelValue', 'ready'],
     setup(props, { emit }) {
       const textarea = ref(null);
       const code = ref(props.modelValue);
       let coder;
       watch(
         () => props.modelValue,
         (val) => {
-          coder?.setValue(val);
+          const currentValue = coder?.getValue();
+          if (val !== currentValue) {
+            coder?.setValue(val);
+          }
         },
       );
+
+      // Get mode based on prop
+      const getModeString = () => {
+        if (props.mode === 'bydbql') {
+          return 'text/x-bydbql';
+        }
+        if (props.mode === 'yaml') {
+          return 'text/x-yaml';
+        }
+        if (props.mode === 'css') {
+          return 'text/css';
+        }
+        return 'text/x-yaml';
+      };
+
       const options = {
-        mode: 'text/x-yaml',
+        mode: getModeString(),
         tabSize: 2,
         theme: props.theme,
         lineNumbers: true,
         line: true,
         readOnly: props.readonly,
         lint: props.lint,
-        gutters: ['CodeMirror-lint-markers'],
+        gutters: props.lint ? ['CodeMirror-lint-markers'] : [],
         styleActiveLine: props.styleActiveLine,
         autoRefresh: props.autoRefresh,
         height: '500px',
+        extraKeys: props.extraKeys,
       };
+
       const initialize = async () => {
         try {
-          /*  let theme = `codemirror/theme/${props.theme}.css`
-        await import(theme) */
           if (props.lint) {
             await import('codemirror/addon/lint/lint.js');
             await import('codemirror/addon/lint/lint.css');
           }
-          /* if (props.mode) {
-          await import(`codemirror/mode/${props.mode}/${props.mode}.js`)
-        } */
           if (props.autoRefresh) {
             await import('codemirror/addon/display/autorefresh');
           }
           if (props.styleActiveLine) {
             await import('codemirror/addon/selection/active-line');
           }
-        } catch (e) {}
+          if (props.enableHint) {
+            await import('codemirror/addon/hint/show-hint.js');
+            await import('codemirror/addon/hint/show-hint.css');
+          }
+        } catch (e) {
+          console.error('Error loading CodeMirror addons:', e);
+        }
+
         coder = CodeMirror.fromTextArea(textarea.value, options);
+
         coder.on('blur', (coder) => {
           const newValue = coder.getValue();
           emit('update:modelValue', newValue);
         });
+
+        // Enable autocomplete on Ctrl+Space or when typing
+        if (props.enableHint) {
+          coder.on('keyup', (cm, event) => {
+            // Don't show hints for special keys
+            const excludedKeys = [
+              8, // Backspace
+              9, // Tab
+              13, // Enter
+              16,
+              17,
+              18, // Shift, Ctrl, Alt
+              20, // Caps Lock
+              27, // Escape
+              33,
+              34,
+              35,
+              36,
+              37,
+              38,
+              39,
+              40, // Page/Arrow keys
+            ];
+
+            if (!cm.state.completionActive && 
!excludedKeys.includes(event.keyCode)) {
+              CodeMirror.commands.autocomplete(cm, null, { completeSingle: 
false });
+            }
+          });

Review Comment:
   The autocomplete is triggered on every keyup event that isn't in the 
excluded list. This could cause performance issues with large datasets or slow 
API responses, as hints will be generated and displayed on every character 
typed. Consider adding debouncing or throttling to reduce the frequency of hint 
generation, especially when `schemasAndGroups` contains large amounts of data.



##########
ui/src/components/CodeMirror/bydbql-mode.js:
##########
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import CodeMirror from 'codemirror';
+
+// Define BydbQL mode extending SQL
+CodeMirror.defineMode('bydbql', function (config) {
+  const sqlMode = CodeMirror.getMode(config, 'text/x-sql');
+
+  const entityTypes = {
+    STREAM: true,
+    MEASURE: true,
+    TRACE: true,
+    PROPERTY: true,
+    TOPN: true,
+  };
+
+  // BydbQL-specific keywords
+  const bydbqlKeywords = {
+    SELECT: true,
+    FROM: true,
+    WHERE: true,
+    ORDER: true,
+    BY: true,
+    ASC: true,
+    DESC: true,
+    LIMIT: true,
+    OFFSET: true,
+    AND: true,
+    OR: true,
+    NOT: true,
+    IN: true,
+    LIKE: true,
+    BETWEEN: true,
+    IS: true,
+    NULL: true,
+    TRUE: true,
+    FALSE: true,
+    AS: true,
+    DISTINCT: true,
+    ALL: true,
+    ANY: true,
+    SOME: true,
+    EXISTS: true,
+    CASE: true,
+    WHEN: true,
+    THEN: true,
+    ELSE: true,
+    END: true,
+    UNION: true,
+    INTERSECT: true,
+    EXCEPT: true,
+    GROUP: true,
+    HAVING: true,
+    TIME: true,
+    SHOW: true,
+    TOP: true,
+    AGGREGATE: true,
+    BY: true,
+    IN: true,
+    NOT: true,
+    MATCH: true,
+    SUM: true,
+    MEAN: true,
+    AVG: true,
+    COUNT: true,
+    MAX: true,
+    MIN: true,
+    TAG: true,
+  };

Review Comment:
   Duplicate keyword entries detected. The keywords 'BY', 'IN', and 'NOT' are 
defined multiple times in the object (lines 40, 75, 76, and lines 48, 76, 77). 
In JavaScript objects, duplicate keys will result in only the last value being 
kept, but this creates confusion and is a maintainability issue.



##########
ui/src/components/CodeMirror/bydbql-mode.js:
##########
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import CodeMirror from 'codemirror';
+
+// Define BydbQL mode extending SQL
+CodeMirror.defineMode('bydbql', function (config) {
+  const sqlMode = CodeMirror.getMode(config, 'text/x-sql');
+
+  const entityTypes = {
+    STREAM: true,
+    MEASURE: true,
+    TRACE: true,
+    PROPERTY: true,
+    TOPN: true,
+  };
+
+  // BydbQL-specific keywords
+  const bydbqlKeywords = {
+    SELECT: true,
+    FROM: true,
+    WHERE: true,
+    ORDER: true,
+    BY: true,
+    ASC: true,
+    DESC: true,
+    LIMIT: true,
+    OFFSET: true,
+    AND: true,
+    OR: true,
+    NOT: true,
+    IN: true,

Review Comment:
   This property is duplicated [in a later property](1).



##########
ui/src/components/CodeMirror/bydbql-mode.js:
##########
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import CodeMirror from 'codemirror';
+
+// Define BydbQL mode extending SQL
+CodeMirror.defineMode('bydbql', function (config) {
+  const sqlMode = CodeMirror.getMode(config, 'text/x-sql');

Review Comment:
   The bydbql mode depends on the SQL mode ('text/x-sql') being available, but 
there's no explicit check to ensure the SQL mode is loaded before defining the 
bydbql mode. If the SQL mode import (line 31 in index.vue) fails or loads after 
this file, `CodeMirror.getMode(config, 'text/x-sql')` could return an undefined 
or default mode, causing the bydbql mode to not work correctly.
   ```suggestion
     const sqlMode = CodeMirror.getMode(config, 'text/x-sql');
     if (!sqlMode || typeof sqlMode.token !== 'function') {
       throw new Error(
         "CodeMirror SQL mode ('text/x-sql') must be loaded before defining the 
BydbQL mode. Please ensure the SQL mode is imported first."
       );
     }
   ```



-- 
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