├── .npmignore ├── src ├── sql.grammar.d.ts ├── sql.grammar.terms.d.ts ├── sql.grammar ├── README.md ├── complete.ts ├── tokens.ts └── sql.ts ├── .gitignore ├── .github └── workflows │ └── dispatch.yml ├── LICENSE ├── package.json ├── test ├── test-tokens.ts └── test-complete.ts ├── CHANGELOG.md └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /test 3 | /node_modules 4 | .tern-* 5 | rollup.config.js 6 | tsconfig.json 7 | -------------------------------------------------------------------------------- /src/sql.grammar.d.ts: -------------------------------------------------------------------------------- 1 | import {LRParser} from "@lezer/lr" 2 | export declare const parser: LRParser 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | package-lock.json 3 | /dist 4 | /test/*.js 5 | /test/*.d.ts 6 | /test/*.d.ts.map 7 | .tern-* 8 | -------------------------------------------------------------------------------- /src/sql.grammar.terms.d.ts: -------------------------------------------------------------------------------- 1 | export const whitespace: number, LineComment: number, BlockComment: number, 2 | String: number, Number: number, Bool: number, Null: number, 3 | ParenL: number, ParenR: number, BraceL: number, BraceR: number, BracketL: number, BracketR: number, Semi: number, Dot: number, 4 | Operator: number, Punctuation: number, SpecialVar: number, Identifier: number, QuotedIdentifier: number, 5 | Keyword: number, Type: number, Builtin: number, Bits: number, Bytes: number 6 | -------------------------------------------------------------------------------- /.github/workflows/dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Trigger CI 2 | on: push 3 | 4 | jobs: 5 | build: 6 | name: Dispatch to main repo 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Emit repository_dispatch 10 | uses: mvasigh/dispatch-action@main 11 | with: 12 | # You should create a personal access token and store it in your repository 13 | token: ${{ secrets.DISPATCH_AUTH }} 14 | repo: dev 15 | owner: codemirror 16 | event_type: push 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018-2021 by Marijn Haverbeke and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@codemirror/lang-sql", 3 | "version": "6.10.0", 4 | "description": "SQL language support for the CodeMirror code editor", 5 | "scripts": { 6 | "test": "cm-runtests", 7 | "prepare": "cm-buildhelper src/sql.ts" 8 | }, 9 | "keywords": [ 10 | "editor", 11 | "code" 12 | ], 13 | "author": { 14 | "name": "Marijn Haverbeke", 15 | "email": "marijn@haverbeke.berlin", 16 | "url": "http://marijnhaverbeke.nl" 17 | }, 18 | "type": "module", 19 | "main": "dist/index.cjs", 20 | "exports": { 21 | "import": "./dist/index.js", 22 | "require": "./dist/index.cjs" 23 | }, 24 | "types": "dist/index.d.ts", 25 | "module": "dist/index.js", 26 | "sideEffects": false, 27 | "license": "MIT", 28 | "dependencies": { 29 | "@codemirror/autocomplete": "^6.0.0", 30 | "@lezer/common": "^1.2.0", 31 | "@lezer/highlight": "^1.0.0", 32 | "@codemirror/language": "^6.0.0", 33 | "@codemirror/state": "^6.0.0", 34 | "@lezer/lr": "^1.0.0" 35 | }, 36 | "devDependencies": { 37 | "@codemirror/buildhelper": "^1.0.0" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/codemirror/lang-sql.git" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/sql.grammar: -------------------------------------------------------------------------------- 1 | @precedence { dot } 2 | 3 | @top Script { 4 | Statement { element* Semi }* 5 | Statement { element+ }? 6 | } 7 | 8 | @skip { whitespace | LineComment | BlockComment } 9 | 10 | element { 11 | String | 12 | Number | 13 | Bool | 14 | Null | 15 | Identifier | 16 | QuotedIdentifier | 17 | Bits | 18 | Bytes | 19 | Builtin | 20 | SpecialVar | 21 | CompositeIdentifier { 22 | Dot? (QuotedIdentifier | Identifier | SpecialVar) (!dot Dot (QuotedIdentifier | Identifier | SpecialVar))+ 23 | } | 24 | Keyword | 25 | Type | 26 | Operator | 27 | Punctuation | 28 | Parens { ParenL element* ParenR } | 29 | Braces { BraceL element* BraceR } | 30 | Brackets { BracketL element* BracketR } 31 | } 32 | 33 | @external tokens tokens from "./tokens" { 34 | whitespace 35 | LineComment[isolate] 36 | BlockComment[isolate] 37 | String[isolate] 38 | Number 39 | Bool 40 | Null 41 | ParenL[@name="("] 42 | ParenR[@name=")"] 43 | BraceL[@name="{"] 44 | BraceR[@name="}"] 45 | BracketL[@name="["] 46 | BracketR[@name="]"] 47 | Semi[@name=";"] 48 | Dot[@name="."] 49 | Operator 50 | Punctuation 51 | SpecialVar 52 | Identifier 53 | QuotedIdentifier[isolate] 54 | Keyword 55 | Type 56 | Bits 57 | Bytes 58 | Builtin 59 | } 60 | 61 | @detectDelim 62 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @codemirror/lang-sql [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-sql.svg)](https://www.npmjs.org/package/@codemirror/lang-sql) 4 | 5 | [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-sql/blob/main/CHANGELOG.md) ] 6 | 7 | This package implements SQL language support for the 8 | [CodeMirror](https://codemirror.net/) code editor. 9 | 10 | The [project page](https://codemirror.net/) has more information, a 11 | number of [examples](https://codemirror.net/examples/) and the 12 | [documentation](https://codemirror.net/docs/). 13 | 14 | This code is released under an 15 | [MIT license](https://github.com/codemirror/lang-sql/tree/main/LICENSE). 16 | 17 | We aim to be an inclusive, welcoming community. To make that explicit, 18 | we have a [code of 19 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 20 | to communication around the project. 21 | 22 | ## Usage 23 | 24 | ```javascript 25 | import {EditorView, basicSetup} from "codemirror" 26 | import {sql} from "@codemirror/lang-sql" 27 | 28 | const view = new EditorView({ 29 | parent: document.body, 30 | doc: `select * from users where age > 20`, 31 | extensions: [basicSetup, sql()] 32 | }) 33 | ``` 34 | 35 | Use `sql({dialect: PostgreSQL})` or similar to select a specific SQL 36 | dialect. 37 | 38 | ## API Reference 39 | 40 | @sql 41 | 42 | @SQLConfig 43 | 44 | @SQLNamespace 45 | 46 | @SQLDialect 47 | 48 | @SQLDialectSpec 49 | 50 | @StandardSQL 51 | @PostgreSQL 52 | @MySQL 53 | @MariaSQL 54 | @MSSQL 55 | @SQLite 56 | @Cassandra 57 | @PLSQL 58 | 59 | @keywordCompletionSource 60 | @schemaCompletionSource 61 | -------------------------------------------------------------------------------- /test/test-tokens.ts: -------------------------------------------------------------------------------- 1 | import ist from "ist" 2 | import {PostgreSQL, MySQL, PLSQL, SQLDialect, MSSQL} from "@codemirror/lang-sql" 3 | 4 | const mysqlTokens = MySQL.language 5 | const postgresqlTokens = PostgreSQL.language 6 | const bigQueryTokens = SQLDialect.define({ 7 | treatBitsAsBytes: true 8 | }).language 9 | const plsqlTokens = PLSQL.language 10 | const mssqlTokens = MSSQL.language 11 | 12 | describe("Parse MySQL tokens", () => { 13 | const parser = mysqlTokens.parser 14 | 15 | it("parses quoted bit-value literals", () => { 16 | ist(parser.parse("SELECT b'0101'"), 'Script(Statement(Keyword,Bits))') 17 | }) 18 | 19 | it("parses unquoted bit-value literals", () => { 20 | ist(parser.parse("SELECT 0b01"), 'Script(Statement(Keyword,Bits))') 21 | }) 22 | }) 23 | 24 | describe("Parse PostgreSQL tokens", () => { 25 | const parser = postgresqlTokens.parser 26 | 27 | it("parses quoted bit-value literals", () => { 28 | ist(parser.parse("SELECT b'0101'"), 'Script(Statement(Keyword,Bits))') 29 | }) 30 | 31 | it("parses quoted bit-value literals", () => { 32 | ist(parser.parse("SELECT B'0101'"), 'Script(Statement(Keyword,Bits))') 33 | }) 34 | 35 | it("parses double dollar quoted string literals", () => { 36 | ist(parser.parse("SELECT $$hello$$"), 'Script(Statement(Keyword,String))') 37 | }) 38 | 39 | it("parses tagged double dollar quoted string literals", () => { 40 | ist(parser.parse("SELECT $body$\nhello\n$body$"), 'Script(Statement(Keyword,String))') 41 | }) 42 | }) 43 | 44 | describe("Parse BigQuery tokens", () => { 45 | const parser = bigQueryTokens.parser 46 | 47 | it("parses quoted bytes literals in single quotes", () => { 48 | ist(parser.parse("SELECT b'abcd'"), 'Script(Statement(Keyword,Bytes))') 49 | }) 50 | 51 | it("parses quoted bytes literals in double quotes", () => { 52 | ist(parser.parse('SELECT b"abcd"'), 'Script(Statement(Keyword,Bytes))') 53 | }) 54 | 55 | it("parses bytes literals in single quotes", () => { 56 | ist(parser.parse("SELECT b'0101'"), 'Script(Statement(Keyword,Bytes))') 57 | }) 58 | 59 | it("parses bytes literals in double quotes", () => { 60 | ist(parser.parse('SELECT b"0101"'), 'Script(Statement(Keyword,Bytes))') 61 | }) 62 | }) 63 | 64 | describe("Parse PL/SQL tokens", () => { 65 | const parser = plsqlTokens.parser 66 | 67 | it("parses alternative quoting mechanism - []", () => { 68 | ist(parser.parse("SELECT q'[foo'bar]' FROM DUAL"), 'Script(Statement(Keyword,String,Keyword,Identifier))') 69 | }) 70 | 71 | it("parses alternative quoting mechanism - {}", () => { 72 | ist(parser.parse("SELECT q'{foo'bar}' FROM DUAL"), 'Script(Statement(Keyword,String,Keyword,Identifier))') 73 | }) 74 | 75 | it("parses alternative quoting mechanism - <>", () => { 76 | ist(parser.parse("SELECT q'' FROM DUAL"), 'Script(Statement(Keyword,String,Keyword,Identifier))') 77 | }) 78 | 79 | it("parses alternative quoting mechanism - ()", () => { 80 | ist(parser.parse("SELECT q'(foo'bar)' FROM DUAL"), 'Script(Statement(Keyword,String,Keyword,Identifier))') 81 | }) 82 | 83 | it("parses alternative quoting mechanism - custom", () => { 84 | ist(parser.parse("SELECT q'~foo'bar~' FROM DUAL"), 'Script(Statement(Keyword,String,Keyword,Identifier))') 85 | }) 86 | 87 | it("parses alternative quoting mechanism - uppercase Q", () => { 88 | ist(parser.parse("SELECT Q'~foo'bar~' FROM DUAL"), 'Script(Statement(Keyword,String,Keyword,Identifier))') 89 | }) 90 | 91 | it("parses alternative quoting mechanism - unclosed", () => { 92 | ist(parser.parse("SELECT q'~foo'bar' FROM DUAL"), 'Script(Statement(Keyword,String))') 93 | }) 94 | }) 95 | 96 | describe("Parse MSSQL tokens", () => { 97 | const parser = mssqlTokens.parser; 98 | 99 | it("parses brackets as QuotedIdentifier", () => { 100 | ist(parser.parse("SELECT * FROM [tblTest]"), "Script(Statement(Keyword,Operator,Keyword,QuotedIdentifier))") 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 6.10.0 (2025-09-16) 2 | 3 | ### New features 4 | 5 | Allow `[` in `identifierQuotes` for MSSQL-style bracketed identifiers. 6 | 7 | ## 6.9.1 (2025-07-28) 8 | 9 | ### Bug fixes 10 | 11 | Include more MSSQL keyboards and builtins in the completions. 12 | 13 | Allow built-in special variables for a dialect to be completed. 14 | 15 | ## 6.9.0 (2025-05-30) 16 | 17 | ### New features 18 | 19 | The new `SQLDialect.configureLanguage` method can be used to configure the language (and it's syntax node props) used by a dialect. 20 | 21 | ## 6.8.0 (2024-10-01) 22 | 23 | ### New features 24 | 25 | The new `keywordCompletion` option can be used to define what kind of completions are generated for keywords. 26 | 27 | ## 6.7.1 (2024-08-21) 28 | 29 | ### Bug fixes 30 | 31 | Remove single-letter words from the list of Postgres keywords, since they interfere with alias-based autocompletion. 32 | 33 | ## 6.7.0 (2024-06-24) 34 | 35 | ### New features 36 | 37 | Dialects can now disable quoting of identifiers containing upper-case characters with the `caseInsensitiveIdentifiers` option. 38 | 39 | ## 6.6.5 (2024-06-17) 40 | 41 | ### Bug fixes 42 | 43 | Fix a bug that broke tokenizing of `e'\n'`-style strings. 44 | 45 | ## 6.6.4 (2024-05-04) 46 | 47 | ### Bug fixes 48 | 49 | Make statement folding leave the entire first line visible. 50 | 51 | Fix a null dereference in schema-based autocompletion. 52 | 53 | ## 6.6.3 (2024-04-08) 54 | 55 | ### Bug fixes 56 | 57 | Fix a bug where Postgres-style dollar-quoted strings were enabled for all dialects, and the `doubleDollarQuotedStrings` options was ignored. 58 | 59 | ## 6.6.2 (2024-03-23) 60 | 61 | ### Bug fixes 62 | 63 | Properly support tags in PostgreSQL `4073` quoted strings. 64 | 65 | ## 6.6.1 (2024-03-04) 66 | 67 | ### Bug fixes 68 | 69 | Fix an issue that caused completions to be missing when using the `defaultSchema` option. 70 | 71 | ## 6.6.0 (2024-02-29) 72 | 73 | ### Bug fixes 74 | 75 | Don't tokenize identifiers after periods as anything but plain identifiers. 76 | 77 | ### New features 78 | 79 | The `schema` option now allows nested objects to define multiple levels of completions, as well as `self` completion options for specific levels. The old format (using `tables`/`schemas`) continues to work but is deprecated. 80 | 81 | ## 6.5.5 (2023-12-28) 82 | 83 | ### Bug fixes 84 | 85 | Make sure table and column completions with upper-case characters are quoted. 86 | 87 | Tag comments and strings as isolating for the purpose of bidirectional text. 88 | 89 | ## 6.5.4 (2023-08-10) 90 | 91 | ### Bug fixes 92 | 93 | Remove use of negative lookbehind in a regular expression, which recent versions of Safari still don't support. 94 | 95 | ## 6.5.3 (2023-08-05) 96 | 97 | ### Bug fixes 98 | 99 | The PL/SQL dialect now correctly handles `q'[]'`-quoting syntax. 100 | 101 | ## 6.5.2 (2023-06-23) 102 | 103 | ### Bug fixes 104 | 105 | Allow table names to contain multiple dots in the schema passed to `schemaCompletionSource`. 106 | 107 | ## 6.5.1 (2023-06-21) 108 | 109 | ### Bug fixes 110 | 111 | `schemaCompletionSource` now adds quotes around non-word identifiers even if the user didn't type a starting quote. 112 | 113 | ## 6.5.0 (2023-05-16) 114 | 115 | ### New features 116 | 117 | Dialect objects now have a public `spec` property holding their configuration. 118 | 119 | ## 6.4.1 (2023-04-13) 120 | 121 | ### Bug fixes 122 | 123 | Fix a bug where tokenizing of block comments got confused when nested comment start/end markers appeared directly next to each other. 124 | 125 | ## 6.4.0 (2023-01-23) 126 | 127 | ### Bug fixes 128 | 129 | Fix syntax tree node names for curly and square brackets, which had their names swapped. 130 | 131 | ### New features 132 | 133 | The new `schemas` config option can be used to provide custom completion objects for schema completions. 134 | 135 | ## 6.3.3 (2022-11-14) 136 | 137 | ### Bug fixes 138 | 139 | Fix tokenizing of double-`$` strings in SQL dialects that support them. 140 | 141 | ## 6.3.2 (2022-10-24) 142 | 143 | ### Bug fixes 144 | 145 | Make sure the language object has a name. 146 | 147 | ## 6.3.1 (2022-10-17) 148 | 149 | ### Bug fixes 150 | 151 | Fix tokenizing of `--` line comments. 152 | 153 | ## 6.3.0 (2022-08-23) 154 | 155 | ### New features 156 | 157 | Schema-based completion now understands basic table alias syntax, and will take it into account when looking up completions. 158 | 159 | ## 6.2.0 (2022-08-14) 160 | 161 | ### New features 162 | 163 | The new `unquotedBitLiterals` dialect option controls whether `0b01` syntax is recognized. 164 | 165 | Dialects now allow a `treatBitsAsBytes` option to allow any characters inside quoted strings prefixed with `b`. 166 | 167 | ## 6.1.0 (2022-08-05) 168 | 169 | ### New features 170 | 171 | The new `doubleDollarQuotedStrings` options to SQL dialects allows parsing of text delimited by `$$` as strings. Regenerate readme 172 | 173 | ## 6.0.0 (2022-06-08) 174 | 175 | ### Breaking changes 176 | 177 | Update dependencies to 6.0.0 178 | 179 | ## 0.20.4 (2022-05-30) 180 | 181 | ### New features 182 | 183 | Schema completion descriptions may now include dots in table names to indicate nested schemas. 184 | 185 | ## 0.20.3 (2022-05-27) 186 | 187 | ### Bug fixes 188 | 189 | Fix a bug where the slash at the end of block comments wasn't considered part of the comment token. 190 | 191 | ## 0.20.2 (2022-05-24) 192 | 193 | ### Bug fixes 194 | 195 | Fix an infinite recursion bug in `schemaCompletionSource`. 196 | 197 | ## 0.20.1 (2022-05-24) 198 | 199 | ### Breaking changes 200 | 201 | The `schemaCompletion` and `keywordCompletion` exports, which returned extensions, have been replaced with `schemaCompletionSource` and `keywordCompletionSource`, which return completion sources. The old exports will remain available until the next major version. 202 | 203 | ## 0.20.0 (2022-04-20) 204 | 205 | ### Bug fixes 206 | 207 | Fix autocompletion on columns when the table name is written with upper-case letters. Move to @lezer/highlight 208 | 209 | ## 0.19.4 (2021-10-28) 210 | 211 | ### Bug fixes 212 | 213 | Remove duplicate keywords/types in dialect configurations. 214 | 215 | Fix a bug that caused characters directly before a space to be tokenized incorrectly. 216 | 217 | ## 0.19.3 (2021-08-21) 218 | 219 | ### Bug fixes 220 | 221 | Fix a bug that broke tokenization of keywords. 222 | 223 | ## 0.19.2 (2021-08-11) 224 | 225 | ## 0.19.1 (2021-08-11) 226 | 227 | ### Bug fixes 228 | 229 | Fix incorrect versions for @lezer dependencies. 230 | 231 | ## 0.19.0 (2021-08-11) 232 | 233 | ### Breaking changes 234 | 235 | Update dependencies to 0.19.0 236 | 237 | ## 0.18.0 (2021-03-03) 238 | 239 | ### Breaking changes 240 | 241 | Update dependencies to 0.18. 242 | 243 | ## 0.17.2 (2021-02-01) 244 | 245 | ### Bug fixes 246 | 247 | Fix bad syntax tree creation when the input ends with an unfinished quoted identifier. 248 | 249 | ## 0.17.1 (2021-01-06) 250 | 251 | ### New features 252 | 253 | The package now also exports a CommonJS module. 254 | 255 | ## 0.17.0 (2020-12-29) 256 | 257 | ### Breaking changes 258 | 259 | First numbered release. 260 | 261 | -------------------------------------------------------------------------------- /src/complete.ts: -------------------------------------------------------------------------------- 1 | import {Completion, CompletionContext, CompletionSource, completeFromList, ifNotIn} from "@codemirror/autocomplete" 2 | import {EditorState, Text} from "@codemirror/state" 3 | import {syntaxTree} from "@codemirror/language" 4 | import {SyntaxNode} from "@lezer/common" 5 | import {Type, Keyword} from "./sql.grammar.terms" 6 | import {type SQLDialect, SQLNamespace} from "./sql" 7 | 8 | function tokenBefore(tree: SyntaxNode) { 9 | let cursor = tree.cursor().moveTo(tree.from, -1) 10 | while (/Comment/.test(cursor.name)) cursor.moveTo(cursor.from, -1) 11 | return cursor.node 12 | } 13 | 14 | function idName(doc: Text, node: SyntaxNode): string { 15 | let text = doc.sliceString(node.from, node.to) 16 | let quoted = /^([`'"\[])(.*)([`'"\]])$/.exec(text) 17 | return quoted ? quoted[2] : text 18 | } 19 | 20 | function plainID(node: SyntaxNode | null) { 21 | return node && (node.name == "Identifier" || node.name == "QuotedIdentifier") 22 | } 23 | 24 | function pathFor(doc: Text, id: SyntaxNode) { 25 | if (id.name == "CompositeIdentifier") { 26 | let path = [] 27 | for (let ch = id.firstChild; ch; ch = ch.nextSibling) 28 | if (plainID(ch)) path.push(idName(doc, ch)) 29 | return path 30 | } 31 | return [idName(doc, id)] 32 | } 33 | 34 | function parentsFor(doc: Text, node: SyntaxNode | null) { 35 | for (let path = [];;) { 36 | if (!node || node.name != ".") return path 37 | let name = tokenBefore(node) 38 | if (!plainID(name)) return path 39 | path.unshift(idName(doc, name)) 40 | node = tokenBefore(name) 41 | } 42 | } 43 | 44 | function sourceContext(state: EditorState, startPos: number) { 45 | let pos = syntaxTree(state).resolveInner(startPos, -1) 46 | let aliases = getAliases(state.doc, pos) 47 | if (pos.name == "Identifier" || pos.name == "QuotedIdentifier" || pos.name == "Keyword") { 48 | return {from: pos.from, 49 | quoted: pos.name == "QuotedIdentifier" ? state.doc.sliceString(pos.from, pos.from + 1) : null, 50 | parents: parentsFor(state.doc, tokenBefore(pos)), 51 | aliases} 52 | } if (pos.name == ".") { 53 | return {from: startPos, quoted: null, parents: parentsFor(state.doc, pos), aliases} 54 | } else { 55 | return {from: startPos, quoted: null, parents: [], empty: true, aliases} 56 | } 57 | } 58 | 59 | const EndFrom = new Set("where group having order union intersect except all distinct limit offset fetch for".split(" ")) 60 | 61 | function getAliases(doc: Text, at: SyntaxNode) { 62 | let statement 63 | for (let parent: SyntaxNode | null = at; !statement; parent = parent.parent) { 64 | if (!parent) return null 65 | if (parent.name == "Statement") statement = parent 66 | } 67 | let aliases = null 68 | for (let scan = statement.firstChild, sawFrom = false, prevID: SyntaxNode | null = null; scan; scan = scan.nextSibling) { 69 | let kw = scan.name == "Keyword" ? doc.sliceString(scan.from, scan.to).toLowerCase() : null 70 | let alias = null 71 | if (!sawFrom) { 72 | sawFrom = kw == "from" 73 | } else if (kw == "as" && prevID && plainID(scan.nextSibling)) { 74 | alias = idName(doc, scan.nextSibling!) 75 | } else if (kw && EndFrom.has(kw)) { 76 | break 77 | } else if (prevID && plainID(scan)) { 78 | alias = idName(doc, scan) 79 | } 80 | if (alias) { 81 | if (!aliases) aliases = Object.create(null) 82 | aliases[alias] = pathFor(doc, prevID!) 83 | } 84 | prevID = /Identifier$/.test(scan.name) ? scan : null 85 | } 86 | return aliases 87 | } 88 | 89 | function maybeQuoteCompletions(openingQuote: string, closingQuote: string, completions: readonly Completion[]) { 90 | return completions.map(c => ({...c, label: c.label[0] == openingQuote ? c.label : openingQuote + c.label + closingQuote, apply: undefined})) 91 | } 92 | 93 | const Span = /^\w*$/, QuotedSpan = /^[`'"\[]?\w*[`'"\]]?$/ 94 | 95 | function isSelfTag(namespace: SQLNamespace): namespace is {self: Completion, children: SQLNamespace} { 96 | return (namespace as any).self && typeof (namespace as any).self.label == "string" 97 | } 98 | 99 | class CompletionLevel { 100 | list: Completion[] = [] 101 | children: {[name: string]: CompletionLevel} | undefined = undefined 102 | 103 | constructor(readonly idQuote: string, readonly idCaseInsensitive: boolean) {} 104 | 105 | child(name: string) { 106 | let children = this.children || (this.children = Object.create(null)) 107 | let found = children[name] 108 | if (found) return found 109 | if (name && !this.list.some(c => c.label == name)) this.list.push(nameCompletion(name, "type", this.idQuote, this.idCaseInsensitive)) 110 | return (children[name] = new CompletionLevel(this.idQuote, this.idCaseInsensitive)) 111 | } 112 | 113 | maybeChild(name: string) { 114 | return this.children ? this.children[name] : null 115 | } 116 | 117 | addCompletion(option: Completion) { 118 | let found = this.list.findIndex(o => o.label == option.label) 119 | if (found > -1) this.list[found] = option 120 | else this.list.push(option) 121 | } 122 | 123 | addCompletions(completions: readonly (Completion | string)[]) { 124 | for (let option of completions) 125 | this.addCompletion(typeof option == "string" ? nameCompletion(option, "property", this.idQuote, this.idCaseInsensitive) : option) 126 | } 127 | 128 | addNamespace(namespace: SQLNamespace) { 129 | if (Array.isArray(namespace)) { 130 | this.addCompletions(namespace) 131 | } else if (isSelfTag(namespace)) { 132 | this.addNamespace(namespace.children) 133 | } else { 134 | this.addNamespaceObject(namespace as {[name: string]: SQLNamespace}) 135 | } 136 | } 137 | 138 | addNamespaceObject(namespace: {[name: string]: SQLNamespace}) { 139 | for (let name of Object.keys(namespace)) { 140 | let children = namespace[name], self: Completion | null = null 141 | let parts = name.replace(/\\?\./g, p => p == "." ? "\0" : p).split("\0") 142 | let scope = this 143 | if (isSelfTag(children)) { 144 | self = children.self 145 | children = children.children 146 | } 147 | for (let i = 0; i < parts.length; i++) { 148 | if (self && i == parts.length - 1) scope.addCompletion(self) 149 | scope = scope.child(parts[i].replace(/\\\./g, ".")) 150 | } 151 | scope.addNamespace(children) 152 | } 153 | } 154 | } 155 | 156 | function nameCompletion(label: string, type: string, idQuote: string, idCaseInsensitive: boolean): Completion { 157 | if ((new RegExp("^[a-z_][a-z_\\d]*$", idCaseInsensitive ? "i" : "")).test(label)) return {label, type} 158 | 159 | return {label, type, apply: idQuote + label + getClosingQuote(idQuote)} 160 | } 161 | 162 | function getClosingQuote(openingQuote: string) { 163 | return openingQuote === "[" ? "]" : openingQuote 164 | } 165 | 166 | // Some of this is more gnarly than it has to be because we're also 167 | // supporting the deprecated, not-so-well-considered style of 168 | // supplying the schema (dotted property names for schemas, separate 169 | // `tables` and `schemas` completions). 170 | export function completeFromSchema(schema: SQLNamespace, 171 | tables?: readonly Completion[], schemas?: readonly Completion[], 172 | defaultTableName?: string, defaultSchemaName?: string, 173 | dialect?: SQLDialect): CompletionSource { 174 | let idQuote = dialect?.spec.identifierQuotes?.[0] || '"' 175 | let top = new CompletionLevel(idQuote, !!dialect?.spec.caseInsensitiveIdentifiers) 176 | let defaultSchema = defaultSchemaName ? top.child(defaultSchemaName) : null 177 | top.addNamespace(schema) 178 | if (tables) (defaultSchema || top).addCompletions(tables) 179 | if (schemas) top.addCompletions(schemas) 180 | if (defaultSchema) top.addCompletions(defaultSchema.list) 181 | if (defaultTableName) top.addCompletions((defaultSchema || top).child(defaultTableName).list) 182 | 183 | return (context: CompletionContext) => { 184 | let {parents, from, quoted, empty, aliases} = sourceContext(context.state, context.pos) 185 | if (empty && !context.explicit) return null 186 | if (aliases && parents.length == 1) parents = aliases[parents[0]] || parents 187 | let level = top 188 | for (let name of parents) { 189 | while (!level.children || !level.children[name]) { 190 | if (level == top && defaultSchema) level = defaultSchema 191 | else if (level == defaultSchema && defaultTableName) level = level.child(defaultTableName) 192 | else return null 193 | } 194 | let next = level.maybeChild(name) 195 | if (!next) return null 196 | level = next 197 | } 198 | 199 | let options = level.list 200 | if (level == top && aliases) 201 | options = options.concat(Object.keys(aliases).map(name => ({label: name, type: "constant"}))) 202 | 203 | if (quoted) { 204 | let openingQuote = quoted[0] 205 | let closingQuote = getClosingQuote(openingQuote) 206 | 207 | let quoteAfter = context.state.sliceDoc(context.pos, context.pos + 1) == closingQuote 208 | 209 | return { 210 | from, 211 | to: quoteAfter ? context.pos + 1 : undefined, 212 | options: maybeQuoteCompletions(openingQuote, closingQuote, options), 213 | validFor: QuotedSpan, 214 | } 215 | } else { 216 | return { 217 | from, 218 | options: options, 219 | validFor: Span 220 | } 221 | } 222 | } 223 | } 224 | 225 | function completionType(tokenType: number) { 226 | return tokenType == Type ? "type" : tokenType == Keyword ? "keyword" : "variable" 227 | } 228 | 229 | export function completeKeywords(keywords: {[name: string]: number}, upperCase: boolean, 230 | build: (name: string, type: string) => Completion) { 231 | let completions = Object.keys(keywords) 232 | .map(keyword => build(upperCase ? keyword.toUpperCase() : keyword, completionType(keywords[keyword]))) 233 | return ifNotIn(["QuotedIdentifier", "String", "LineComment", "BlockComment", "."], 234 | completeFromList(completions)) 235 | } 236 | -------------------------------------------------------------------------------- /test/test-complete.ts: -------------------------------------------------------------------------------- 1 | import {EditorState} from "@codemirror/state" 2 | import {CompletionContext, CompletionResult, CompletionSource} from "@codemirror/autocomplete" 3 | import {schemaCompletionSource, keywordCompletionSource, PostgreSQL, MySQL, SQLConfig, SQLDialect, MSSQL} from "@codemirror/lang-sql" 4 | import ist from "ist" 5 | 6 | function get(doc: string, conf: SQLConfig & {explicit?: boolean, keywords?: boolean} = {}) { 7 | let cur = doc.indexOf("|"), dialect = conf.dialect || PostgreSQL 8 | doc = doc.slice(0, cur) + doc.slice(cur + 1) 9 | let state = EditorState.create({ 10 | doc, 11 | selection: {anchor: cur}, 12 | extensions: [dialect, dialect.language.data.of({ 13 | autocomplete: conf.keywords 14 | ? keywordCompletionSource(dialect, conf.upperCaseKeywords, conf.keywordCompletion) 15 | : schemaCompletionSource(Object.assign({dialect}, conf)) 16 | })] 17 | }) 18 | let result = state.languageDataAt("autocomplete", cur)[0](new CompletionContext(state, cur, !!conf.explicit)) 19 | return result as CompletionResult | null 20 | } 21 | 22 | function str(result: CompletionResult | null) { 23 | return !result ? "" : result.options.slice() 24 | .sort((a, b) => (b.boost || 0) - (a.boost || 0) || (a.label < b.label ? -1 : 1)) 25 | .map(o => o.apply || o.label) 26 | .join(", ") 27 | } 28 | 29 | let schema1 = { 30 | users: ["name", "id", "address"], 31 | products: ["name", "cost", "description"] 32 | } 33 | 34 | let schema2 = { 35 | "public.users": ["email", "id"], 36 | "other.users": ["name", "id"] 37 | } 38 | 39 | describe("SQL completion", () => { 40 | it("completes table names", () => { 41 | ist(str(get("select u|", {schema: schema1})), "products, users") 42 | }) 43 | 44 | it("completes quoted table names", () => { 45 | ist(str(get('select "u|', {schema: schema1})), '"products", "users"') 46 | }) 47 | 48 | it("completes table names under schema", () => { 49 | ist(str(get("select public.u|", {schema: schema2})), "users") 50 | }) 51 | 52 | it("completes quoted table names under schema", () => { 53 | ist(str(get('select public."u|', {schema: schema2})), '"users"') 54 | }) 55 | 56 | it("completes quoted table names under quoted schema", () => { 57 | ist(str(get('select "public"."u|', {schema: schema2})), '"users"') 58 | }) 59 | 60 | it("completes column names", () => { 61 | ist(str(get("select users.|", {schema: schema1})), "address, id, name") 62 | }) 63 | 64 | it("completes quoted column names", () => { 65 | ist(str(get('select users."|', {schema: schema1})), '"address", "id", "name"') 66 | }) 67 | 68 | it("completes column names in quoted tables", () => { 69 | ist(str(get('select "users".|', {schema: schema1})), "address, id, name") 70 | }) 71 | 72 | it("completes column names in tables for a specific schema", () => { 73 | ist(str(get("select public.users.|", {schema: schema2})), "email, id") 74 | ist(str(get("select other.users.|", {schema: schema2})), "id, name") 75 | }) 76 | 77 | it("completes quoted column names in tables for a specific schema", () => { 78 | ist(str(get('select public.users."|', {schema: schema2})), '"email", "id"') 79 | ist(str(get('select other.users."|', {schema: schema2})), '"id", "name"') 80 | }) 81 | 82 | it("completes column names in quoted tables for a specific schema", () => { 83 | ist(str(get('select public."users".|', {schema: schema2})), "email, id") 84 | ist(str(get('select other."users".|', {schema: schema2})), "id, name") 85 | }) 86 | 87 | it("completes column names in quoted tables for a specific quoted schema", () => { 88 | ist(str(get('select "public"."users".|', {schema: schema2})), "email, id") 89 | ist(str(get('select "other"."users".|', {schema: schema2})), "id, name") 90 | }) 91 | 92 | it("completes quoted column names in quoted tables for a specific quoted schema", () => { 93 | ist(str(get('select "public"."users"."|', {schema: schema2})), '"email", "id"') 94 | ist(str(get('select "other"."users"."|', {schema: schema2})), '"id", "name"') 95 | }) 96 | 97 | it("completes column names in bracket quoted tables with MSSQL", () => { 98 | ist(str(get("select [public].[users].|", {schema: schema2, dialect: MSSQL})), "email, id") 99 | ist(str(get("select [other].[users].|", {schema: schema2, dialect: MSSQL})), "id, name") 100 | }) 101 | 102 | it("completes column names of aliased tables", () => { 103 | ist(str(get("select u.| from users u", {schema: schema1})), "address, id, name") 104 | ist(str(get("select u.| from users as u", {schema: schema1})), "address, id, name") 105 | ist(str(get("select u.| from (SELECT * FROM something u) join users u", {schema: schema1})), "address, id, name") 106 | ist(str(get("select * from users u where u.|", {schema: schema1})), "address, id, name") 107 | ist(str(get("select * from users as u where u.|", {schema: schema1})), "address, id, name") 108 | ist(str(get("select * from (SELECT * FROM something u) join users u where u.|", {schema: schema1})), "address, id, name") 109 | }) 110 | 111 | it("completes column names of aliased quoted tables", () => { 112 | ist(str(get('select u.| from "users" u', {schema: schema1})), "address, id, name") 113 | ist(str(get('select u.| from "users" as u', {schema: schema1})), "address, id, name") 114 | ist(str(get('select * from "users" u where u.|', {schema: schema1})), "address, id, name") 115 | ist(str(get('select * from "users" as u where u.|', {schema: schema1})), "address, id, name") 116 | }) 117 | 118 | it("completes column names of aliased tables for a specific schema", () => { 119 | ist(str(get("select u.| from public.users u", {schema: schema2})), "email, id") 120 | }) 121 | 122 | it("completes column names in aliased quoted tables for a specific schema", () => { 123 | ist(str(get('select u.| from public."users" u', {schema: schema2})), "email, id") 124 | }) 125 | 126 | it("completes column names in aliased quoted tables for a specific quoted schema", () => { 127 | ist(str(get('select u.| from "public"."users" u', {schema: schema2})), "email, id") 128 | }) 129 | 130 | it("completes aliased table names", () => { 131 | ist(str(get('select a| from a.b as ab join auto au', {schema: schema2})), "ab, au, other, public") 132 | }) 133 | 134 | it("completes bracket quoted aliases with MSSQL", () => { 135 | ist(str(get("select u.| from public.users [u]", {schema: schema2, dialect: MSSQL})), "email, id") 136 | ist(str(get("select [u].| from public.users u", {schema: schema2, dialect: MSSQL})), "email, id") 137 | }) 138 | 139 | it("includes closing quote in completion", () => { 140 | let r = get('select "u|"', {schema: schema1}) 141 | ist(r!.to, 10) 142 | }) 143 | 144 | it("keeps extra table completion properties", () => { 145 | let r = get("select u|", {schema: {users: ["id"]}, tables: [{label: "users", type: "keyword"}]}) 146 | ist(r!.options[0].type, "keyword") 147 | }) 148 | 149 | it("keeps extra column completion properties", () => { 150 | let r = get("select users.|", {schema: {users: [{label: "id", type: "keyword"}]}}) 151 | ist(r!.options[0].type, "keyword") 152 | }) 153 | 154 | it("supports a default table", () => { 155 | ist(str(get("select i|", {schema: schema1, defaultTable: "users"})), "address, id, name, products, users") 156 | }) 157 | 158 | it("supports alternate quoting styles", () => { 159 | ist(str(get("select `u|", {dialect: MySQL, schema: schema1})), "`products`, `users`") 160 | ist(str(get("select [u|", {dialect: MSSQL, schema: schema1})), "[products], [users]") 161 | }) 162 | 163 | it("doesn't complete without identifier", () => { 164 | ist(str(get("select |", {schema: schema1})), "") 165 | }) 166 | 167 | it("does complete explicitly without identifier", () => { 168 | ist(str(get("select |", {schema: schema1, explicit: true})), "products, users") 169 | }) 170 | 171 | it("adds identifiers for non-word completions", () => { 172 | ist(str(get("foo.b|", {schema: {foo: ["b c", "b-c", "bup"]}, dialect: PostgreSQL})), 173 | '"b c", "b-c", bup') 174 | ist(str(get("foo.b|", {schema: {foo: ["b c", "b-c", "bup"]}, dialect: MySQL})), 175 | '`b c`, `b-c`, bup') 176 | }) 177 | 178 | it("adds identifiers for upper case completions", () => { 179 | ist(str(get("foo.c|", {schema: {foo: ["Column", "cell"]}, dialect: PostgreSQL})), 180 | '"Column", cell') 181 | 182 | const customDialect = SQLDialect.define({...PostgreSQL.spec, caseInsensitiveIdentifiers: true}) 183 | ist(str(get("foo.c|", {schema: {foo: ["Column", "cell"]}, dialect: customDialect})), 184 | 'Column, cell') 185 | }) 186 | 187 | it("can add MSSQL-style brackets as identifier quotes", () => { 188 | let dialect = SQLDialect.define({...MSSQL.spec, identifierQuotes: '['}) 189 | let config = { schema: {"Foo": ["Bar"]}, dialect: dialect } 190 | 191 | ist(str(get("[Foo].b|", config)), "[Bar]") 192 | }) 193 | 194 | it("supports nesting more than two deep", () => { 195 | let s = {schema: {"one.two.three": ["four"]}} 196 | ist(str(get("o|", s)), "one") 197 | ist(str(get("one.|", s)), "two") 198 | ist(str(get("one.two.|", s)), "three") 199 | ist(str(get("one.two.three.|", s)), "four") 200 | }) 201 | 202 | it("supports escaped dots in table names", () => { 203 | let s = {schema: {"db\\.conf": ["abc"]}} 204 | ist(str(get("db|", s)), '"db.conf"') 205 | ist(str(get('"db.conf".|', s)), "abc") 206 | }) 207 | 208 | it("supports nested schema declarations", () => { 209 | let s = {schema: { 210 | public: {users: ["email", "id"]}, 211 | other: {users: ["name", "id"]}, 212 | plain: ["one", "two"] 213 | }} 214 | ist(str(get("pl|", s)), "other, plain, public") 215 | ist(str(get("plain.|", s)), "one, two") 216 | ist(str(get("public.u|", s)), "users") 217 | ist(str(get("public.users.e|", s)), "email, id") 218 | }) 219 | 220 | it("supports self fields to specify table/schema completions", () => { 221 | let s: SQLConfig = {schema: { 222 | foo: { 223 | self: {label: "foo", type: "keyword"}, 224 | children: { 225 | bar: { 226 | self: {label: "bar", type: "constant"}, 227 | children: ["a", "b"] 228 | } 229 | } 230 | } 231 | }} 232 | ist(get("select f|", s)!.options[0].type, "keyword") 233 | ist(get("select foo.|", s)!.options[0].type, "constant") 234 | ist(get("select foo.|", s)!.options.length, 1) 235 | }) 236 | 237 | it("can complete keywords", () => { 238 | ist(get("s|", {keywords: true})!.options.some(c => c.label == "select")) 239 | }) 240 | 241 | it("can complete upper-case keywords", () => { 242 | ist(get("s|", {keywords: true, upperCaseKeywords: true})!.options.some(c => c.label == "SELECT")) 243 | }) 244 | 245 | it("can transform keyword completions", () => { 246 | ist(get("s|", {keywords: true, keywordCompletion: l => ({label: l, type: "x"})})!.options.every(c => c.type == "x")) 247 | }) 248 | }) 249 | -------------------------------------------------------------------------------- /src/tokens.ts: -------------------------------------------------------------------------------- 1 | import {ExternalTokenizer, InputStream} from "@lezer/lr" 2 | import {whitespace, LineComment, BlockComment, String as StringToken, Number, Bits, Bytes, Bool, Null, 3 | ParenL, ParenR, BraceL, BraceR, BracketL, BracketR, Semi, Dot, 4 | Operator, Punctuation, SpecialVar, Identifier, QuotedIdentifier, 5 | Keyword, Type, Builtin} from "./sql.grammar.terms" 6 | 7 | const enum Ch { 8 | Newline = 10, 9 | Space = 32, 10 | DoubleQuote = 34, 11 | Hash = 35, 12 | Dollar = 36, 13 | SingleQuote = 39, 14 | ParenL = 40, ParenR = 41, 15 | Star = 42, 16 | Plus = 43, 17 | Comma = 44, 18 | Dash = 45, 19 | Dot = 46, 20 | Slash = 47, 21 | Colon = 58, 22 | Semi = 59, 23 | Question = 63, 24 | At = 64, 25 | BracketL = 91, BracketR = 93, 26 | Backslash = 92, 27 | Underscore = 95, 28 | Backtick = 96, 29 | BraceL = 123, BraceR = 125, 30 | 31 | A = 65, a = 97, 32 | B = 66, b = 98, 33 | E = 69, e = 101, 34 | F = 70, f = 102, 35 | N = 78, n = 110, 36 | Q = 81, q = 113, 37 | X = 88, x = 120, 38 | Z = 90, z = 122, 39 | 40 | _0 = 48, _1 = 49, _9 = 57, 41 | } 42 | 43 | function isAlpha(ch: number) { 44 | return ch >= Ch.A && ch <= Ch.Z || ch >= Ch.a && ch <= Ch.z || ch >= Ch._0 && ch <= Ch._9 45 | } 46 | 47 | function isHexDigit(ch: number) { 48 | return ch >= Ch._0 && ch <= Ch._9 || ch >= Ch.a && ch <= Ch.f || ch >= Ch.A && ch <= Ch.F 49 | } 50 | 51 | function readLiteral(input: InputStream, endQuote: number, backslashEscapes: boolean) { 52 | for (let escaped = false;;) { 53 | if (input.next < 0) return 54 | if (input.next == endQuote && !escaped) { input.advance(); return } 55 | escaped = backslashEscapes && !escaped && input.next == Ch.Backslash 56 | input.advance() 57 | } 58 | } 59 | 60 | function readDoubleDollarLiteral(input: InputStream, tag: string) { 61 | scan: for (;;) { 62 | if (input.next < 0) return 63 | if (input.next == Ch.Dollar) { 64 | input.advance() 65 | for (let i = 0; i < tag.length; i++) { 66 | if (input.next != tag.charCodeAt(i)) continue scan 67 | input.advance() 68 | } 69 | if (input.next == Ch.Dollar) { 70 | input.advance() 71 | return 72 | } 73 | } else { 74 | input.advance() 75 | } 76 | } 77 | } 78 | 79 | function readPLSQLQuotedLiteral(input: InputStream, openDelim: number) { 80 | let matchingDelim = "[{<(".indexOf(String.fromCharCode(openDelim)) 81 | let closeDelim = matchingDelim < 0 ? openDelim : "]}>)".charCodeAt(matchingDelim) 82 | 83 | for (;;) { 84 | if (input.next < 0) return 85 | if (input.next == closeDelim && input.peek(1) == Ch.SingleQuote) { 86 | input.advance(2) 87 | return 88 | } 89 | input.advance() 90 | } 91 | } 92 | 93 | function readWord(input: InputStream): void 94 | function readWord(input: InputStream, result: string): string 95 | function readWord(input: InputStream, result?: string) { 96 | for (;;) { 97 | if (input.next != Ch.Underscore && !isAlpha(input.next)) break 98 | if (result != null) result += String.fromCharCode(input.next) 99 | input.advance() 100 | } 101 | return result 102 | } 103 | 104 | function readWordOrQuoted(input: InputStream) { 105 | if (input.next == Ch.SingleQuote || input.next == Ch.DoubleQuote || input.next == Ch.Backtick) { 106 | let quote = input.next 107 | input.advance() 108 | readLiteral(input, quote, false) 109 | } else { 110 | readWord(input) 111 | } 112 | } 113 | 114 | function readBits(input: InputStream, endQuote?: number) { 115 | while ((input as any).next == Ch._0 || (input as any).next == Ch._1) input.advance() 116 | if (endQuote && input.next == endQuote) input.advance() 117 | } 118 | 119 | function readNumber(input: InputStream, sawDot: boolean) { 120 | for (;;) { 121 | if (input.next == Ch.Dot) { 122 | if (sawDot) break 123 | sawDot = true 124 | } else if (input.next < Ch._0 || input.next > Ch._9) { 125 | break 126 | } 127 | input.advance() 128 | } 129 | if (input.next == Ch.E || input.next == Ch.e) { 130 | input.advance() 131 | if ((input as any).next == Ch.Plus || (input as any).next == Ch.Dash) input.advance() 132 | while (input.next >= Ch._0 && input.next <= Ch._9) input.advance() 133 | } 134 | } 135 | 136 | function eol(input: InputStream) { 137 | while (!(input.next < 0 || input.next == Ch.Newline)) input.advance() 138 | } 139 | 140 | function inString(ch: number, str: string) { 141 | for (let i = 0; i < str.length; i++) if (str.charCodeAt(i) == ch) return true 142 | return false 143 | } 144 | 145 | const Space = " \t\r\n" 146 | 147 | function keywords(keywords: string, types: string, builtin?: string) { 148 | let result: {[name: string]: number} = Object.create(null) 149 | result["true"] = result["false"] = Bool 150 | result["null"] = result["unknown"] = Null 151 | for (let kw of keywords.split(" ")) if (kw) result[kw] = Keyword 152 | for (let tp of types.split(" ")) if (tp) result[tp] = Type 153 | for (let kw of (builtin || "").split(" ")) if (kw) result[kw] = Builtin 154 | return result 155 | } 156 | 157 | export interface Dialect { 158 | backslashEscapes: boolean, 159 | hashComments: boolean, 160 | spaceAfterDashes: boolean, 161 | slashComments: boolean, 162 | doubleQuotedStrings: boolean, 163 | doubleDollarQuotedStrings: boolean, 164 | unquotedBitLiterals: boolean, 165 | treatBitsAsBytes: boolean, 166 | charSetCasts: boolean, 167 | plsqlQuotingMechanism: boolean, 168 | operatorChars: string, 169 | specialVar: string, 170 | identifierQuotes: string, 171 | caseInsensitiveIdentifiers: boolean, 172 | words: {[name: string]: number} 173 | } 174 | 175 | export const SQLTypes = "array binary bit boolean char character clob date decimal double float int integer interval large national nchar nclob numeric object precision real smallint time timestamp varchar varying " 176 | export const SQLKeywords = "absolute action add after all allocate alter and any are as asc assertion at authorization before begin between both breadth by call cascade cascaded case cast catalog check close collate collation column commit condition connect connection constraint constraints constructor continue corresponding count create cross cube current current_date current_default_transform_group current_transform_group_for_type current_path current_role current_time current_timestamp current_user cursor cycle data day deallocate declare default deferrable deferred delete depth deref desc describe descriptor deterministic diagnostics disconnect distinct do domain drop dynamic each else elseif end end-exec equals escape except exception exec execute exists exit external fetch first for foreign found from free full function general get global go goto grant group grouping handle having hold hour identity if immediate in indicator initially inner inout input insert intersect into is isolation join key language last lateral leading leave left level like limit local localtime localtimestamp locator loop map match method minute modifies module month names natural nesting new next no none not of old on only open option or order ordinality out outer output overlaps pad parameter partial path prepare preserve primary prior privileges procedure public read reads recursive redo ref references referencing relative release repeat resignal restrict result return returns revoke right role rollback rollup routine row rows savepoint schema scroll search second section select session session_user set sets signal similar size some space specific specifictype sql sqlexception sqlstate sqlwarning start state static system_user table temporary then timezone_hour timezone_minute to trailing transaction translation treat trigger under undo union unique unnest until update usage user using value values view when whenever where while with without work write year zone " 177 | 178 | const defaults: Dialect = { 179 | backslashEscapes: false, 180 | hashComments: false, 181 | spaceAfterDashes: false, 182 | slashComments: false, 183 | doubleQuotedStrings: false, 184 | doubleDollarQuotedStrings: false, 185 | unquotedBitLiterals: false, 186 | treatBitsAsBytes: false, 187 | charSetCasts: false, 188 | plsqlQuotingMechanism: false, 189 | operatorChars: "*+\-%<>!=&|~^/", 190 | specialVar: "?", 191 | identifierQuotes: '"', 192 | caseInsensitiveIdentifiers: false, 193 | words: keywords(SQLKeywords, SQLTypes) 194 | } 195 | 196 | export function dialect(spec: Partial, kws?: string, types?: string, builtin?: string): Dialect { 197 | let dialect = {} as Dialect 198 | for (let prop in defaults) 199 | (dialect as any)[prop] = ((spec.hasOwnProperty(prop) ? spec : defaults) as any)[prop] 200 | if (kws) dialect.words = keywords(kws, types || "", builtin) 201 | return dialect 202 | } 203 | 204 | export function tokensFor(d: Dialect) { 205 | return new ExternalTokenizer(input => { 206 | let {next} = input 207 | input.advance() 208 | if (inString(next, Space)) { 209 | while (inString(input.next, Space)) input.advance() 210 | input.acceptToken(whitespace) 211 | } else if (next == Ch.Dollar && d.doubleDollarQuotedStrings) { 212 | let tag = readWord(input, "") 213 | if (input.next == Ch.Dollar) { 214 | input.advance() 215 | readDoubleDollarLiteral(input, tag) 216 | input.acceptToken(StringToken) 217 | } 218 | } else if (next == Ch.SingleQuote || next == Ch.DoubleQuote && d.doubleQuotedStrings) { 219 | readLiteral(input, next, d.backslashEscapes) 220 | input.acceptToken(StringToken) 221 | } else if (next == Ch.Hash && d.hashComments || 222 | next == Ch.Slash && input.next == Ch.Slash && d.slashComments) { 223 | eol(input) 224 | input.acceptToken(LineComment) 225 | } else if (next == Ch.Dash && input.next == Ch.Dash && 226 | (!d.spaceAfterDashes || input.peek(1) == Ch.Space)) { 227 | eol(input) 228 | input.acceptToken(LineComment) 229 | } else if (next == Ch.Slash && input.next == Ch.Star) { 230 | input.advance() 231 | for (let depth = 1;;) { 232 | let cur: number = input.next 233 | if (input.next < 0) break 234 | input.advance() 235 | if (cur == Ch.Star && (input as any).next == Ch.Slash) { 236 | depth-- 237 | input.advance() 238 | if (!depth) break 239 | } else if (cur == Ch.Slash && input.next == Ch.Star) { 240 | depth++ 241 | input.advance() 242 | } 243 | } 244 | input.acceptToken(BlockComment) 245 | } else if ((next == Ch.e || next == Ch.E) && input.next == Ch.SingleQuote) { 246 | input.advance() 247 | readLiteral(input, Ch.SingleQuote, true) 248 | input.acceptToken(StringToken) 249 | } else if ((next == Ch.n || next == Ch.N) && input.next == Ch.SingleQuote && 250 | d.charSetCasts) { 251 | input.advance() 252 | readLiteral(input, Ch.SingleQuote, d.backslashEscapes) 253 | input.acceptToken(StringToken) 254 | } else if (next == Ch.Underscore && d.charSetCasts) { 255 | for (let i = 0;; i++) { 256 | if (input.next == Ch.SingleQuote && i > 1) { 257 | input.advance() 258 | readLiteral(input, Ch.SingleQuote, d.backslashEscapes) 259 | input.acceptToken(StringToken) 260 | break 261 | } 262 | if (!isAlpha(input.next)) break 263 | input.advance() 264 | } 265 | } else if (d.plsqlQuotingMechanism && 266 | (next == Ch.q || next == Ch.Q) && input.next == Ch.SingleQuote && 267 | input.peek(1) > 0 && !inString(input.peek(1), Space)) { 268 | let openDelim = input.peek(1) 269 | input.advance(2) 270 | readPLSQLQuotedLiteral(input, openDelim) 271 | input.acceptToken(StringToken) 272 | } else if (inString(next, d.identifierQuotes)) { 273 | const endQuote = next == Ch.BracketL ? Ch.BracketR : next 274 | readLiteral(input, endQuote, false) 275 | input.acceptToken(QuotedIdentifier) 276 | } else if (next == Ch.ParenL) { 277 | input.acceptToken(ParenL) 278 | } else if (next == Ch.ParenR) { 279 | input.acceptToken(ParenR) 280 | } else if (next == Ch.BraceL) { 281 | input.acceptToken(BraceL) 282 | } else if (next == Ch.BraceR) { 283 | input.acceptToken(BraceR) 284 | } else if (next == Ch.BracketL) { 285 | input.acceptToken(BracketL) 286 | } else if (next == Ch.BracketR) { 287 | input.acceptToken(BracketR) 288 | } else if (next == Ch.Semi) { 289 | input.acceptToken(Semi) 290 | } else if (d.unquotedBitLiterals && next == Ch._0 && input.next == Ch.b) { 291 | input.advance() 292 | readBits(input) 293 | input.acceptToken(Bits) 294 | } else if ((next == Ch.b || next == Ch.B) && (input.next == Ch.SingleQuote || input.next == Ch.DoubleQuote)) { 295 | const quoteStyle = input.next 296 | input.advance() 297 | if (d.treatBitsAsBytes) { 298 | readLiteral(input, quoteStyle, d.backslashEscapes) 299 | input.acceptToken(Bytes) 300 | } else { 301 | readBits(input, quoteStyle) 302 | input.acceptToken(Bits) 303 | } 304 | } else if (next == Ch._0 && (input.next == Ch.x || input.next == Ch.X) || 305 | (next == Ch.x || next == Ch.X) && input.next == Ch.SingleQuote) { 306 | let quoted = input.next == Ch.SingleQuote 307 | input.advance() 308 | while (isHexDigit(input.next)) input.advance() 309 | if (quoted && input.next == Ch.SingleQuote) input.advance() 310 | input.acceptToken(Number) 311 | } else if (next == Ch.Dot && input.next >= Ch._0 && input.next <= Ch._9) { 312 | readNumber(input, true) 313 | input.acceptToken(Number) 314 | } else if (next == Ch.Dot) { 315 | input.acceptToken(Dot) 316 | } else if (next >= Ch._0 && next <= Ch._9) { 317 | readNumber(input, false) 318 | input.acceptToken(Number) 319 | } else if (inString(next, d.operatorChars)) { 320 | while (inString(input.next, d.operatorChars)) input.advance() 321 | input.acceptToken(Operator) 322 | } else if (inString(next, d.specialVar)) { 323 | if (input.next == next) input.advance() 324 | readWordOrQuoted(input) 325 | input.acceptToken(SpecialVar) 326 | } else if (next == Ch.Colon || next == Ch.Comma) { 327 | input.acceptToken(Punctuation) 328 | } else if (isAlpha(next)) { 329 | let word = readWord(input, String.fromCharCode(next)) 330 | input.acceptToken(input.next == Ch.Dot || input.peek(-word.length - 1) == Ch.Dot 331 | ? Identifier : d.words[word.toLowerCase()] ?? Identifier) 332 | } 333 | }) 334 | } 335 | 336 | export const tokens = tokensFor(defaults) 337 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @codemirror/lang-sql [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-sql.svg)](https://www.npmjs.org/package/@codemirror/lang-sql) 4 | 5 | [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-sql/blob/main/CHANGELOG.md) ] 6 | 7 | This package implements SQL language support for the 8 | [CodeMirror](https://codemirror.net/) code editor. 9 | 10 | The [project page](https://codemirror.net/) has more information, a 11 | number of [examples](https://codemirror.net/examples/) and the 12 | [documentation](https://codemirror.net/docs/). 13 | 14 | This code is released under an 15 | [MIT license](https://github.com/codemirror/lang-sql/tree/main/LICENSE). 16 | 17 | We aim to be an inclusive, welcoming community. To make that explicit, 18 | we have a [code of 19 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 20 | to communication around the project. 21 | 22 | ## Usage 23 | 24 | ```javascript 25 | import {EditorView, basicSetup} from "codemirror" 26 | import {sql} from "@codemirror/lang-sql" 27 | 28 | const view = new EditorView({ 29 | parent: document.body, 30 | doc: `select * from users where age > 20`, 31 | extensions: [basicSetup, sql()] 32 | }) 33 | ``` 34 | 35 | Use `sql({dialect: PostgreSQL})` or similar to select a specific SQL 36 | dialect. 37 | 38 | ## API Reference 39 | 40 |
41 |
42 | sql(config⁠?: SQLConfig = {}) → LanguageSupport
43 | 44 |

SQL language support for the given SQL dialect, with keyword 45 | completion, and, if provided, schema-based completion as extra 46 | extensions.

47 |
48 |
49 |

50 | interface 51 | SQLConfig

52 |
53 | 54 |

Options used to configure an SQL extension.

55 |
56 | dialect⁠?: SQLDialect
57 | 58 |

The dialect to use. Defaults to 59 | StandardSQL.

60 |
61 | schema⁠?: SQLNamespace
62 | 63 |

You can use this to define the schemas, tables, and their fields 64 | for autocompletion.

65 |
66 | defaultTable⁠?: string
67 | 68 |

When given, columns from the named table can be completed 69 | directly at the top level.

70 |
71 | defaultSchema⁠?: string
72 | 73 |

When given, tables prefixed with this schema name can be 74 | completed directly at the top level.

75 |
76 | upperCaseKeywords⁠?: boolean
77 | 78 |

When set to true, keyword completions will be upper-case.

79 |
80 | keywordCompletion⁠?: fn(labelstring, typestring) → Completion
81 | 82 |

Can be used to customize the completions generated for keywords.

83 |
84 | 85 |
86 |
87 | type 88 | SQLNamespace = Object<SQLNamespace> | {self: Completion, children: SQLNamespace} | readonly (string | Completion)[] 89 |
90 | 91 |

The type used to describe a level of the schema for 92 | completion. Can be an array of 93 | options (columns), an object mapping table or schema names to 94 | deeper levels, or a {self, children} object that assigns a 95 | completion option to use for its parent property, when the default option 96 | (its name as label and type "type") isn't suitable.

97 |
98 |
99 |

100 | class 101 | SQLDialect

102 |
103 | 104 |

Represents an SQL dialect.

105 |
106 | language: LRLanguage
107 | 108 |

The language for this dialect.

109 |
110 | spec: SQLDialectSpec
111 | 112 |

The spec used to define this dialect.

113 |
114 | extension: Extension
115 | 116 |

Returns the language for this dialect as an extension.

117 |
118 | configureLanguage(optionsParserConfig, name⁠?: string) → SQLDialect
119 | 120 |

Reconfigure the parser used by this dialect. Returns a new 121 | dialect object.

122 |
123 | static define(specSQLDialectSpec) → SQLDialect
124 | 125 |

Define a new dialect.

126 |
127 | 128 |
129 |
130 |

131 | type 132 | SQLDialectSpec

133 |
134 | 135 |

Configuration for an SQL Dialect.

136 |
137 | keywords⁠?: string
138 | 139 |

A space-separated list of keywords for the dialect.

140 |
141 | builtin⁠?: string
142 | 143 |

A space-separated string of built-in identifiers for the dialect.

144 |
145 | types⁠?: string
146 | 147 |

A space-separated string of type names for the dialect.

148 |
149 | backslashEscapes⁠?: boolean
150 | 151 |

Controls whether regular strings allow backslash escapes.

152 |
153 | hashComments⁠?: boolean
154 | 155 |

Controls whether # creates a line comment.

156 |
157 | slashComments⁠?: boolean
158 | 159 |

Controls whether // creates a line comment.

160 |
161 | spaceAfterDashes⁠?: boolean
162 | 163 |

When enabled -- comments are only recognized when there's a 164 | space after the dashes.

165 |
166 | doubleDollarQuotedStrings⁠?: boolean
167 | 168 |

When enabled, things quoted with "$" are treated as 169 | strings, rather than identifiers.

170 |
171 | doubleQuotedStrings⁠?: boolean
172 | 173 |

When enabled, things quoted with double quotes are treated as 174 | strings, rather than identifiers.

175 |
176 | charSetCasts⁠?: boolean
177 | 178 |

Enables strings like _utf8'str' or N'str'.

179 |
180 | plsqlQuotingMechanism⁠?: boolean
181 | 182 |

Enables string quoting syntax like q'[str]', as used in 183 | PL/SQL.

184 |
185 | operatorChars⁠?: string
186 | 187 |

The set of characters that make up operators. Defaults to 188 | "*+\-%<>!=&|~^/".

189 |
190 | specialVar⁠?: string
191 | 192 |

The set of characters that start a special variable name. 193 | Defaults to "?".

194 |
195 | identifierQuotes⁠?: string
196 | 197 |

The characters that can be used to quote identifiers. Defaults 198 | to "\"". Add [ for MSSQL-style bracket quoted identifiers.

199 |
200 | caseInsensitiveIdentifiers⁠?: boolean
201 | 202 |

Controls whether identifiers are case-insensitive. Identifiers 203 | with upper-case letters are quoted when set to false (which is 204 | the default).

205 |
206 | unquotedBitLiterals⁠?: boolean
207 | 208 |

Controls whether bit values can be defined as 0b1010. Defaults 209 | to false.

210 |
211 | treatBitsAsBytes⁠?: boolean
212 | 213 |

Controls whether bit values can contain other characters than 0 and 1. 214 | Defaults to false.

215 |
216 | 217 |
218 |
219 | StandardSQL: SQLDialect
220 | 221 |

The standard SQL dialect.

222 |
223 |
224 | PostgreSQL: SQLDialect
225 | 226 |

Dialect for PostgreSQL.

227 |
228 |
229 | MySQL: SQLDialect
230 | 231 |

MySQL dialect.

232 |
233 |
234 | MariaSQL: SQLDialect
235 | 236 |

Variant of MySQL for 237 | MariaDB.

238 |
239 |
240 | MSSQL: SQLDialect
241 | 242 |

SQL dialect for Microsoft SQL 243 | Server.

244 |
245 |
246 | SQLite: SQLDialect
247 | 248 |

SQLite dialect.

249 |
250 |
251 | Cassandra: SQLDialect
252 | 253 |

Dialect for Cassandra's SQL-ish query language.

254 |
255 |
256 | PLSQL: SQLDialect
257 | 258 |

PL/SQL dialect.

259 |
260 |
261 | keywordCompletionSource(dialectSQLDialect, upperCase⁠?: boolean = false, build⁠?: fn(labelstring, typestring) → Completion) → CompletionSource
262 | 263 |

Returns a completion source that provides keyword completion for 264 | the given SQL dialect.

265 |
266 |
267 | schemaCompletionSource(configSQLConfig) → CompletionSource
268 | 269 |

Returns a completion sources that provides schema-based completion 270 | for the given configuration.

271 |
272 |
273 | -------------------------------------------------------------------------------- /src/sql.ts: -------------------------------------------------------------------------------- 1 | import {continuedIndent, indentNodeProp, foldNodeProp, LRLanguage, LanguageSupport} from "@codemirror/language" 2 | import {Extension} from "@codemirror/state" 3 | import {Completion, CompletionSource} from "@codemirror/autocomplete" 4 | import {styleTags, tags as t} from "@lezer/highlight" 5 | import {ParserConfig} from "@lezer/lr" 6 | import {parser as baseParser} from "./sql.grammar" 7 | import {tokens, Dialect, tokensFor, SQLKeywords, SQLTypes, dialect} from "./tokens" 8 | import {completeFromSchema, completeKeywords} from "./complete" 9 | 10 | let parser = baseParser.configure({ 11 | props: [ 12 | indentNodeProp.add({ 13 | Statement: continuedIndent() 14 | }), 15 | foldNodeProp.add({ 16 | Statement(tree, state) { return {from: Math.min(tree.from + 100, state.doc.lineAt(tree.from).to), to: tree.to} }, 17 | BlockComment(tree) { return {from: tree.from + 2, to: tree.to - 2} } 18 | }), 19 | styleTags({ 20 | Keyword: t.keyword, 21 | Type: t.typeName, 22 | Builtin: t.standard(t.name), 23 | Bits: t.number, 24 | Bytes: t.string, 25 | Bool: t.bool, 26 | Null: t.null, 27 | Number: t.number, 28 | String: t.string, 29 | Identifier: t.name, 30 | QuotedIdentifier: t.special(t.string), 31 | SpecialVar: t.special(t.name), 32 | LineComment: t.lineComment, 33 | BlockComment: t.blockComment, 34 | Operator: t.operator, 35 | "Semi Punctuation": t.punctuation, 36 | "( )": t.paren, 37 | "{ }": t.brace, 38 | "[ ]": t.squareBracket 39 | }) 40 | ] 41 | }) 42 | 43 | /// Configuration for an [SQL Dialect](#lang-sql.SQLDialect). 44 | export type SQLDialectSpec = { 45 | /// A space-separated list of keywords for the dialect. 46 | keywords?: string, 47 | /// A space-separated string of built-in identifiers for the dialect. 48 | builtin?: string, 49 | /// A space-separated string of type names for the dialect. 50 | types?: string, 51 | /// Controls whether regular strings allow backslash escapes. 52 | backslashEscapes?: boolean, 53 | /// Controls whether # creates a line comment. 54 | hashComments?: boolean, 55 | /// Controls whether `//` creates a line comment. 56 | slashComments?: boolean, 57 | /// When enabled `--` comments are only recognized when there's a 58 | /// space after the dashes. 59 | spaceAfterDashes?: boolean, 60 | /// When enabled, things quoted with "$$" are treated as 61 | /// strings, rather than identifiers. 62 | doubleDollarQuotedStrings?: boolean, 63 | /// When enabled, things quoted with double quotes are treated as 64 | /// strings, rather than identifiers. 65 | doubleQuotedStrings?: boolean, 66 | /// Enables strings like `_utf8'str'` or `N'str'`. 67 | charSetCasts?: boolean, 68 | /// Enables string quoting syntax like `q'[str]'`, as used in 69 | /// PL/SQL. 70 | plsqlQuotingMechanism?: boolean, 71 | /// The set of characters that make up operators. Defaults to 72 | /// `"*+\-%<>!=&|~^/"`. 73 | operatorChars?: string, 74 | /// The set of characters that start a special variable name. 75 | /// Defaults to `"?"`. 76 | specialVar?: string, 77 | /// The characters that can be used to quote identifiers. Defaults 78 | /// to `"\""`. Add `[` for MSSQL-style bracket quoted identifiers. 79 | identifierQuotes?: string 80 | /// Controls whether identifiers are case-insensitive. Identifiers 81 | /// with upper-case letters are quoted when set to false (which is 82 | /// the default). 83 | caseInsensitiveIdentifiers?: boolean, 84 | /// Controls whether bit values can be defined as 0b1010. Defaults 85 | /// to false. 86 | unquotedBitLiterals?: boolean, 87 | /// Controls whether bit values can contain other characters than 0 and 1. 88 | /// Defaults to false. 89 | treatBitsAsBytes?: boolean, 90 | } 91 | 92 | /// Represents an SQL dialect. 93 | export class SQLDialect { 94 | private constructor( 95 | /// @internal 96 | readonly dialect: Dialect, 97 | /// The language for this dialect. 98 | readonly language: LRLanguage, 99 | /// The spec used to define this dialect. 100 | readonly spec: SQLDialectSpec 101 | ) {} 102 | 103 | /// Returns the language for this dialect as an extension. 104 | get extension() { return this.language.extension } 105 | 106 | /// Reconfigure the parser used by this dialect. Returns a new 107 | /// dialect object. 108 | configureLanguage(options: ParserConfig, name?: string) { 109 | return new SQLDialect(this.dialect, this.language.configure(options, name), this.spec) 110 | } 111 | 112 | /// Define a new dialect. 113 | static define(spec: SQLDialectSpec) { 114 | let d = dialect(spec, spec.keywords, spec.types, spec.builtin) 115 | let language = LRLanguage.define({ 116 | name: "sql", 117 | parser: parser.configure({ 118 | tokenizers: [{from: tokens, to: tokensFor(d)}] 119 | }), 120 | languageData: { 121 | commentTokens: {line: "--", block: {open: "/*", close: "*/"}}, 122 | closeBrackets: {brackets: ["(", "[", "{", "'", '"', "`"]} 123 | } 124 | }) 125 | return new SQLDialect(d, language, spec) 126 | } 127 | } 128 | 129 | /// The type used to describe a level of the schema for 130 | /// [completion](#lang-sql.SQLConfig.schema). Can be an array of 131 | /// options (columns), an object mapping table or schema names to 132 | /// deeper levels, or a `{self, children}` object that assigns a 133 | /// completion option to use for its parent property, when the default option 134 | /// (its name as label and type `"type"`) isn't suitable. 135 | export type SQLNamespace = {[name: string]: SQLNamespace} 136 | | {self: Completion, children: SQLNamespace} 137 | | readonly (Completion | string)[] 138 | 139 | /// Options used to configure an SQL extension. 140 | export interface SQLConfig { 141 | /// The [dialect](#lang-sql.SQLDialect) to use. Defaults to 142 | /// [`StandardSQL`](#lang-sql.StandardSQL). 143 | dialect?: SQLDialect, 144 | /// You can use this to define the schemas, tables, and their fields 145 | /// for autocompletion. 146 | schema?: SQLNamespace, 147 | /// @hide 148 | tables?: readonly Completion[], 149 | /// @hide 150 | schemas?: readonly Completion[], 151 | /// When given, columns from the named table can be completed 152 | /// directly at the top level. 153 | defaultTable?: string, 154 | /// When given, tables prefixed with this schema name can be 155 | /// completed directly at the top level. 156 | defaultSchema?: string, 157 | /// When set to true, keyword completions will be upper-case. 158 | upperCaseKeywords?: boolean 159 | /// Can be used to customize the completions generated for keywords. 160 | keywordCompletion?: (label: string, type: string) => Completion 161 | } 162 | 163 | function defaultKeyword(label: string, type: string) { return {label, type, boost: -1} } 164 | 165 | /// Returns a completion source that provides keyword completion for 166 | /// the given SQL dialect. 167 | export function keywordCompletionSource(dialect: SQLDialect, upperCase = false, 168 | build?: (label: string, type: string) => Completion): CompletionSource { 169 | return completeKeywords(dialect.dialect.words, upperCase, build || defaultKeyword) 170 | } 171 | 172 | /// Returns a completion sources that provides schema-based completion 173 | /// for the given configuration. 174 | export function schemaCompletionSource(config: SQLConfig): CompletionSource { 175 | return config.schema ? completeFromSchema(config.schema, config.tables, config.schemas, 176 | config.defaultTable, config.defaultSchema, 177 | config.dialect || StandardSQL) 178 | : () => null 179 | } 180 | 181 | function schemaCompletion(config: SQLConfig): Extension { 182 | return config.schema ? (config.dialect || StandardSQL).language.data.of({ 183 | autocomplete: schemaCompletionSource(config) 184 | }) : [] 185 | } 186 | 187 | /// SQL language support for the given SQL dialect, with keyword 188 | /// completion, and, if provided, schema-based completion as extra 189 | /// extensions. 190 | export function sql(config: SQLConfig = {}) { 191 | let lang = config.dialect || StandardSQL 192 | return new LanguageSupport(lang.language, [ 193 | schemaCompletion(config), 194 | lang.language.data.of({ 195 | autocomplete: keywordCompletionSource(lang, config.upperCaseKeywords, config.keywordCompletion) 196 | }) 197 | ]) 198 | } 199 | 200 | /// The standard SQL dialect. 201 | export const StandardSQL = SQLDialect.define({}) 202 | 203 | /// Dialect for [PostgreSQL](https://www.postgresql.org). 204 | export const PostgreSQL = SQLDialect.define({ 205 | charSetCasts: true, 206 | doubleDollarQuotedStrings: true, 207 | operatorChars: "+-*/<>=~!@#%^&|`?", 208 | specialVar: "", 209 | keywords: SQLKeywords + "abort abs absent access according ada admin aggregate alias also always analyse analyze array_agg array_max_cardinality asensitive assert assignment asymmetric atomic attach attribute attributes avg backward base64 begin_frame begin_partition bernoulli bit_length blocked bom cache called cardinality catalog_name ceil ceiling chain char_length character_length character_set_catalog character_set_name character_set_schema characteristics characters checkpoint class class_origin cluster coalesce cobol collation_catalog collation_name collation_schema collect column_name columns command_function command_function_code comment comments committed concurrently condition_number configuration conflict connection_name constant constraint_catalog constraint_name constraint_schema contains content control conversion convert copy corr cost covar_pop covar_samp csv cume_dist current_catalog current_row current_schema cursor_name database datalink datatype datetime_interval_code datetime_interval_precision db debug defaults defined definer degree delimiter delimiters dense_rank depends derived detach detail dictionary disable discard dispatch dlnewcopy dlpreviouscopy dlurlcomplete dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly dlurlpathwrite dlurlscheme dlurlserver dlvalue document dump dynamic_function dynamic_function_code element elsif empty enable encoding encrypted end_frame end_partition endexec enforced enum errcode error event every exclude excluding exclusive exp explain expression extension extract family file filter final first_value flag floor following force foreach fortran forward frame_row freeze fs functions fusion generated granted greatest groups handler header hex hierarchy hint id ignore ilike immediately immutable implementation implicit import include including increment indent index indexes info inherit inherits inline insensitive instance instantiable instead integrity intersection invoker isnull key_member key_type label lag last_value lead leakproof least length library like_regex link listen ln load location lock locked log logged lower mapping matched materialized max max_cardinality maxvalue member merge message message_length message_octet_length message_text min minvalue mod mode more move multiset mumps name namespace nfc nfd nfkc nfkd nil normalize normalized nothing notice notify notnull nowait nth_value ntile nullable nullif nulls number occurrences_regex octet_length octets off offset oids operator options ordering others over overlay overriding owned owner parallel parameter_mode parameter_name parameter_ordinal_position parameter_specific_catalog parameter_specific_name parameter_specific_schema parser partition pascal passing passthrough password percent percent_rank percentile_cont percentile_disc perform period permission pg_context pg_datatype_name pg_exception_context pg_exception_detail pg_exception_hint placing plans pli policy portion position position_regex power precedes preceding prepared print_strict_params procedural procedures program publication query quote raise range rank reassign recheck recovery refresh regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex rename repeatable replace replica requiring reset respect restart restore result_oid returned_cardinality returned_length returned_octet_length returned_sqlstate returning reverse routine_catalog routine_name routine_schema routines row_count row_number rowtype rule scale schema_name schemas scope scope_catalog scope_name scope_schema security selective self sensitive sequence sequences serializable server server_name setof share show simple skip slice snapshot source specific_name sqlcode sqlerror sqrt stable stacked standalone statement statistics stddev_pop stddev_samp stdin stdout storage strict strip structure style subclass_origin submultiset subscription substring substring_regex succeeds sum symmetric sysid system system_time table_name tables tablesample tablespace temp template ties token top_level_count transaction_active transactions_committed transactions_rolled_back transform transforms translate translate_regex trigger_catalog trigger_name trigger_schema trim trim_array truncate trusted type types uescape unbounded uncommitted unencrypted unlink unlisten unlogged unnamed untyped upper uri use_column use_variable user_defined_type_catalog user_defined_type_code user_defined_type_name user_defined_type_schema vacuum valid validate validator value_of var_pop var_samp varbinary variable_conflict variadic verbose version versioning views volatile warning whitespace width_bucket window within wrapper xmlagg xmlattributes xmlbinary xmlcast xmlcomment xmlconcat xmldeclaration xmldocument xmlelement xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltext xmlvalidate yes", 210 | types: SQLTypes + "bigint int8 bigserial serial8 varbit bool box bytea cidr circle precision float8 inet int4 json jsonb line lseg macaddr macaddr8 money numeric pg_lsn point polygon float4 int2 smallserial serial2 serial serial4 text timetz timestamptz tsquery tsvector txid_snapshot uuid xml" 211 | }) 212 | 213 | const MySQLKeywords = "accessible algorithm analyze asensitive authors auto_increment autocommit avg avg_row_length binlog btree cache catalog_name chain change changed checkpoint checksum class_origin client_statistics coalesce code collations columns comment committed completion concurrent consistent contains contributors convert database databases day_hour day_microsecond day_minute day_second delay_key_write delayed delimiter des_key_file dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile enable enclosed ends engine engines enum errors escaped even event events every explain extended fast field fields flush force found_rows fulltext grants handler hash high_priority hosts hour_microsecond hour_minute hour_second ignore ignore_server_ids import index index_statistics infile innodb insensitive insert_method install invoker iterate keys kill linear lines list load lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modify mutex mysql_errno no_write_to_binlog offline offset one online optimize optionally outfile pack_keys parser partition partitions password phase plugin plugins prev processlist profile profiles purge query quick range read_write rebuild recover regexp relaylog remove rename reorganize repair repeatable replace require resume rlike row_format rtree schedule schema_name schemas second_microsecond security sensitive separator serializable server share show slave slow snapshot soname spatial sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result ssl starting starts std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace terminated triggers truncate uncommitted uninstall unlock upgrade use use_frm user_resources user_statistics utc_date utc_time utc_timestamp variables views warnings xa xor year_month zerofill" 214 | 215 | const MySQLTypes = SQLTypes + "bool blob long longblob longtext medium mediumblob mediumint mediumtext tinyblob tinyint tinytext text bigint int1 int2 int3 int4 int8 float4 float8 varbinary varcharacter precision datetime unsigned signed" 216 | 217 | const MySQLBuiltin = "charset clear edit ego help nopager notee nowarning pager print prompt quit rehash source status system tee" 218 | 219 | /// [MySQL](https://dev.mysql.com/) dialect. 220 | export const MySQL = SQLDialect.define({ 221 | operatorChars: "*+-%<>!=&|^", 222 | charSetCasts: true, 223 | doubleQuotedStrings: true, 224 | unquotedBitLiterals: true, 225 | hashComments: true, 226 | spaceAfterDashes: true, 227 | specialVar: "@?", 228 | identifierQuotes: "`", 229 | keywords: SQLKeywords + "group_concat " + MySQLKeywords, 230 | types: MySQLTypes, 231 | builtin: MySQLBuiltin 232 | }) 233 | 234 | /// Variant of [`MySQL`](#lang-sql.MySQL) for 235 | /// [MariaDB](https://mariadb.org/). 236 | export const MariaSQL = SQLDialect.define({ 237 | operatorChars: "*+-%<>!=&|^", 238 | charSetCasts: true, 239 | doubleQuotedStrings: true, 240 | unquotedBitLiterals: true, 241 | hashComments: true, 242 | spaceAfterDashes: true, 243 | specialVar: "@?", 244 | identifierQuotes: "`", 245 | keywords: SQLKeywords + "always generated groupby_concat hard persistent shutdown soft virtual " + MySQLKeywords, 246 | types: MySQLTypes, 247 | builtin: MySQLBuiltin 248 | }) 249 | 250 | let MSSQLBuiltin = 251 | // Aggregate https://msdn.microsoft.com/en-us/library/ms173454.aspx 252 | "approx_count_distinct approx_percentile_cont approx_percentile_disc avg checksum_agg count count_big grouping grouping_id max min product stdev stdevp sum var varp " + 253 | // AI https://learn.microsoft.com/en-us/sql/t-sql/functions/ai-functions-transact-sql?view=sql-server-ver17 254 | "ai_generate_embeddings ai_generate_chunks " + 255 | // Analytic https://learn.microsoft.com/en-us/sql/t-sql/functions/analytic-functions-transact-sql?view=sql-server-ver17 256 | "cume_dist first_value lag last_value lead percentile_cont percentile_disc percent_rank " + 257 | // Bit Manipulation https://learn.microsoft.com/en-us/sql/t-sql/functions/bit-manipulation-functions-overview?view=sql-server-ver17 258 | "left_shift right_shift bit_count get_bit set_bit " + 259 | // Collation Functions https://learn.microsoft.com/en-us/sql/t-sql/functions/collation-functions-collationproperty-transact-sql?view=sql-server-ver17 260 | "collationproperty tertiary_weights " + 261 | // Configuration https://learn.microsoft.com/en-us/sql/t-sql/functions/configuration-functions-transact-sql?view=sql-server-ver17 262 | "@@datefirst @@dbts @@langid @@language @@lock_timeout @@max_connections @@max_precision @@nestlevel @@options @@remserver @@servername @@servicename @@spid @@textsize @@version " + 263 | // Conversion https://learn.microsoft.com/en-us/sql/t-sql/functions/conversion-functions-transact-sql?view=sql-server-ver17 264 | "cast convert parse try_cast try_convert try_parse " + 265 | // Cryptographic https://learn.microsoft.com/en-us/sql/t-sql/functions/cryptographic-functions-transact-sql?view=sql-server-ver17 266 | "asymkey_id asymkeyproperty certproperty cert_id crypt_gen_random decryptbyasymkey decryptbycert decryptbykey decryptbykeyautoasymkey decryptbykeyautocert decryptbypassphrase encryptbyasymkey encryptbycert encryptbykey encryptbypassphrase hashbytes is_objectsigned key_guid key_id key_name signbyasymkey signbycert symkeyproperty verifysignedbycert verifysignedbyasymkey " + 267 | // Cursor https://learn.microsoft.com/en-us/sql/t-sql/functions/cursor-functions-transact-sql?view=sql-server-ver17 268 | "@@cursor_rows @@fetch_status cursor_status " + 269 | // Data type https://learn.microsoft.com/en-us/sql/t-sql/functions/data-type-functions-transact-sql?view=sql-server-ver17 270 | "datalength ident_current ident_incr ident_seed identity sql_variant_property " + 271 | // Date & time https://learn.microsoft.com/en-us/sql/t-sql/functions/date-and-time-data-types-and-functions-transact-sql?view=sql-server-ver17 272 | "@@datefirst current_timestamp current_timezone current_timezone_id date_bucket dateadd datediff datediff_big datefromparts datename datepart datetime2fromparts datetimefromparts datetimeoffsetfromparts datetrunc day eomonth getdate getutcdate isdate month smalldatetimefromparts switchoffset sysdatetime sysdatetimeoffset sysutcdatetime timefromparts todatetimeoffset year " + 273 | // Fuzzy string match https://learn.microsoft.com/en-us/sql/t-sql/functions/edit-distance-transact-sql?view=sql-server-ver17 274 | "edit_distance edit_distance_similarity jaro_winkler_distance jaro_winkler_similarity " + 275 | // Graph https://learn.microsoft.com/en-us/sql/t-sql/functions/graph-functions-transact-sql?view=sql-server-ver17 276 | "edge_id_from_parts graph_id_from_edge_id graph_id_from_node_id node_id_from_parts object_id_from_edge_id object_id_from_node_id " + 277 | // JSON https://learn.microsoft.com/en-us/sql/t-sql/functions/json-functions-transact-sql?view=sql-server-ver17 278 | "json isjson json_array json_contains json_modify json_object json_path_exists json_query json_value " + 279 | // Regular Expressions https://learn.microsoft.com/en-us/sql/t-sql/functions/regular-expressions-functions-transact-sql?view=sql-server-ver17 280 | "regexp_like regexp_replace regexp_substr regexp_instr regexp_count regexp_matches regexp_split_to_table " + 281 | // Mathematical https://learn.microsoft.com/en-us/sql/t-sql/functions/mathematical-functions-transact-sql?view=sql-server-ver17 282 | "abs acos asin atan atn2 ceiling cos cot degrees exp floor log log10 pi power radians rand round sign sin sqrt square tan " + 283 | // Logical https://learn.microsoft.com/en-us/sql/t-sql/functions/logical-functions-choose-transact-sql?view=sql-server-ver17 284 | "choose greatest iif least " + 285 | // Metadata https://learn.microsoft.com/en-us/sql/t-sql/functions/metadata-functions-transact-sql?view=sql-server-ver17 286 | "@@procid app_name applock_mode applock_test assemblyproperty col_length col_name columnproperty databasepropertyex db_id db_name file_id file_idex file_name filegroup_id filegroup_name filegroupproperty fileproperty filepropertyex fulltextcatalogproperty fulltextserviceproperty index_col indexkey_property indexproperty next value for object_definition object_id object_name object_schema_name objectproperty objectpropertyex original_db_name parsename schema_id schema_name scope_identity serverproperty stats_date type_id type_name typeproperty " + 287 | // Ranking https://learn.microsoft.com/en-us/sql/t-sql/functions/ranking-functions-transact-sql?view=sql-server-ver17 288 | "dense_rank ntile rank row_number " + 289 | // Replication https://learn.microsoft.com/en-us/sql/t-sql/functions/replication-functions-publishingservername?view=sql-server-ver17 290 | "publishingservername " + 291 | // Security https://learn.microsoft.com/en-us/sql/t-sql/functions/security-functions-transact-sql?view=sql-server-ver17 292 | "certenclosed certprivatekey current_user database_principal_id has_dbaccess has_perms_by_name is_member is_rolemember is_srvrolemember loginproperty original_login permissions pwdencrypt pwdcompare session_user sessionproperty suser_id suser_name suser_sid suser_sname system_user user user_id user_name " + 293 | // String https://learn.microsoft.com/en-us/sql/t-sql/functions/string-functions-transact-sql?view=sql-server-ver17 294 | "ascii char charindex concat concat_ws difference format left len lower ltrim nchar patindex quotename replace replicate reverse right rtrim soundex space str string_agg string_escape stuff substring translate trim unicode upper " + 295 | // System https://learn.microsoft.com/en-us/sql/t-sql/functions/system-functions-transact-sql?view=sql-server-ver17 296 | "$partition @@error @@identity @@pack_received @@rowcount @@trancount binary_checksum checksum compress connectionproperty context_info current_request_id current_transaction_id decompress error_line error_message error_number error_procedure error_severity error_state formatmessage get_filestream_transaction_context getansinull host_id host_name isnull isnumeric min_active_rowversion newid newsequentialid rowcount_big session_context xact_state " + 297 | // System Statistical https://learn.microsoft.com/en-us/sql/t-sql/functions/system-statistical-functions-transact-sql?view=sql-server-ver17 298 | "@@connections @@cpu_busy @@idle @@io_busy @@pack_sent @@packet_errors @@timeticks @@total_errors @@total_read @@total_write " + 299 | // Text & Image https://learn.microsoft.com/en-us/sql/t-sql/functions/text-and-image-functions-textptr-transact-sql?view=sql-server-ver17 300 | "textptr textvalid " + 301 | // Trigger https://learn.microsoft.com/en-us/sql/t-sql/functions/trigger-functions-transact-sql?view=sql-server-ver17 302 | "columns_updated eventdata trigger_nestlevel " + 303 | // Vectors https://learn.microsoft.com/en-us/sql/t-sql/functions/vector-functions-transact-sql?view=sql-server-ver17 304 | "vector_distance vectorproperty vector_search " + 305 | // Relational operators https://msdn.microsoft.com/en-us/library/ms187957.aspx 306 | "generate_series opendatasource openjson openquery openrowset openxml predict string_split " + 307 | // Other 308 | "coalesce nullif apply catch filter force include keep keepfixed modify optimize parameterization parameters partition recompile sequence set" 309 | 310 | /// SQL dialect for Microsoft [SQL 311 | /// Server](https://www.microsoft.com/en-us/sql-server). 312 | export const MSSQL = SQLDialect.define({ 313 | keywords: SQLKeywords + 314 | // Reserved Keywords https://learn.microsoft.com/en-us/sql/t-sql/language-elements/reserved-keywords-transact-sql?view=sql-server-ver17 315 | "add external procedure all fetch public alter file raiserror and fillfactor read any for readtext as foreign reconfigure asc freetext references authorization freetexttable replication backup from restore begin full restrict between function return break goto revert browse grant revoke bulk group right by having rollback cascade holdlock rowcount case identity rowguidcol check identity_insert rule checkpoint identitycol save close if schema clustered in securityaudit coalesce index select collate inner semantickeyphrasetable column insert semanticsimilaritydetailstable commit intersect semanticsimilaritytable compute into session_user constraint is set contains join setuser containstable key shutdown continue kill some convert left statistics create like system_user cross lineno table current load tablesample current_date merge textsize current_time national then current_timestamp nocheck to current_user nonclustered top cursor not tran database null transaction dbcc nullif trigger deallocate of truncate declare off try_convert default offsets tsequal delete on union deny open unique desc opendatasource unpivot disk openquery update distinct openrowset updatetext distributed openxml use double option user drop or values dump order varying else outer view end over waitfor errlvl percent when escape pivot where except plan while exec precision with execute primary within group exists print writetext exit proc " + 316 | // table hints https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver17 317 | "noexpand index forceseek forcescan holdlock nolock nowait paglock readcommitted readcommittedlock readpast readuncommitted repeatableread rowlock serializable snapshot spatial_window_max_cells tablock tablockx updlock xlock keepidentity keepdefaults ignore_constraints ignore_triggers", 318 | types: SQLTypes + "smalldatetime datetimeoffset datetime2 datetime bigint smallint smallmoney tinyint money real text nvarchar ntext varbinary image hierarchyid uniqueidentifier sql_variant xml", 319 | builtin: MSSQLBuiltin, 320 | operatorChars: "*+-%<>!=^&|/", 321 | specialVar: "@", 322 | identifierQuotes: "\"[" 323 | }) 324 | 325 | /// [SQLite](https://sqlite.org/) dialect. 326 | export const SQLite = SQLDialect.define({ 327 | keywords: SQLKeywords + "abort analyze attach autoincrement conflict database detach exclusive fail glob ignore index indexed instead isnull notnull offset plan pragma query raise regexp reindex rename replace temp vacuum virtual", 328 | types: SQLTypes + "bool blob long longblob longtext medium mediumblob mediumint mediumtext tinyblob tinyint tinytext text bigint int2 int8 unsigned signed real", 329 | builtin: "auth backup bail changes clone databases dbinfo dump echo eqp explain fullschema headers help import imposter indexes iotrace lint load log mode nullvalue once print prompt quit restore save scanstats separator shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width", 330 | operatorChars: "*+-%<>!=&|/~", 331 | identifierQuotes: "`\"", 332 | specialVar: "@:?$" 333 | }) 334 | 335 | /// Dialect for [Cassandra](https://cassandra.apache.org/)'s SQL-ish query language. 336 | export const Cassandra = SQLDialect.define({ 337 | keywords: "add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime infinity NaN", 338 | types: SQLTypes + "ascii bigint blob counter frozen inet list map static text timeuuid tuple uuid varint", 339 | slashComments: true 340 | }) 341 | 342 | /// [PL/SQL](https://en.wikipedia.org/wiki/PL/SQL) dialect. 343 | export const PLSQL = SQLDialect.define({ 344 | keywords: SQLKeywords + "abort accept access add all alter and any arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body by case cast char_base check close cluster clusters colauth column comment commit compress connected constant constraint crash create current currval cursor data_base database dba deallocate debugoff debugon declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry exception exception_init exchange exclusive exists external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base of off offline on online only option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw rebuild record ref references refresh rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work", 345 | builtin: "appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define echo editfile embedded feedback flagger flush heading headsep instance linesize lno loboffset logsource longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar repfooter repheader serveroutput shiftinout show showmode spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout timing trimout trimspool ttitle underline verify version wrap", 346 | types: SQLTypes + "ascii bfile bfilename bigserial bit blob dec long number nvarchar nvarchar2 serial smallint string text uid varchar2 xml", 347 | operatorChars: "*/+-%<>!=~", 348 | doubleQuotedStrings: true, 349 | charSetCasts: true, 350 | plsqlQuotingMechanism: true 351 | }) 352 | --------------------------------------------------------------------------------