Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ Defaults to <code>&quot;?&quot;</code>.</p>

<dd><p>The characters that can be used to quote identifiers. Defaults
to <code>&quot;\&quot;&quot;</code>.</p>
</dd><dt id="user-content-sqldialectspec.identifiercaseinsensitive">
<code><strong><a href="#user-content-sqldialectspec.identifiercaseinsensitive">identifiercaseinsensitive</a></strong>&#8288;?: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">boolean</a></code></dt>

<dd><p>Controls whether identifiers are case-insensitive. Identifiers with upper-case letters are quoted then set to false. Defaults
to <code>false</code>.</p>
</dd><dt id="user-content-sqldialectspec.unquotedbitliterals">
<code><strong><a href="#user-content-sqldialectspec.unquotedbitliterals">unquotedBitLiterals</a></strong>&#8288;?: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">boolean</a></code></dt>

Expand Down
15 changes: 8 additions & 7 deletions src/complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ class CompletionLevel {
list: Completion[] = []
children: {[name: string]: CompletionLevel} | undefined = undefined

constructor(readonly idQuote: string) {}
constructor(readonly idQuote: string, readonly idCaseInsensitive?: boolean) {}

child(name: string) {
let children = this.children || (this.children = Object.create(null))
let found = children[name]
if (found) return found
if (name && !this.list.some(c => c.label == name)) this.list.push(nameCompletion(name, "type", this.idQuote))
return (children[name] = new CompletionLevel(this.idQuote))
if (name && !this.list.some(c => c.label == name)) this.list.push(nameCompletion(name, "type", this.idQuote, this.idCaseInsensitive))
return (children[name] = new CompletionLevel(this.idQuote, this.idCaseInsensitive))
}

maybeChild(name: string) {
Expand All @@ -123,7 +123,7 @@ class CompletionLevel {

addCompletions(completions: readonly (Completion | string)[]) {
for (let option of completions)
this.addCompletion(typeof option == "string" ? nameCompletion(option, "property", this.idQuote) : option)
this.addCompletion(typeof option == "string" ? nameCompletion(option, "property", this.idQuote, this.idCaseInsensitive) : option)
}

addNamespace(namespace: SQLNamespace) {
Expand Down Expand Up @@ -154,8 +154,9 @@ class CompletionLevel {
}
}

function nameCompletion(label: string, type: string, idQuote: string): Completion {
if (/^[a-z_][a-z_\d]*$/.test(label)) return {label, type}
function nameCompletion(label: string, type: string, idQuote: string, idCaseInsensitive: boolean): Completion {
const regex = new RegExp("^[a-z_][a-z_\\d]*$", idCaseInsensitive ? 'i' : undefined);
if (regex.test(label)) return {label, type}
return {label, type, apply: idQuote + label + idQuote}
}

Expand All @@ -168,7 +169,7 @@ export function completeFromSchema(schema: SQLNamespace,
defaultTableName?: string, defaultSchemaName?: string,
dialect?: SQLDialect): CompletionSource {
let idQuote = dialect?.spec.identifierQuotes?.[0] || '"'
let top = new CompletionLevel(idQuote)
let top = new CompletionLevel(idQuote, dialect?.spec.identifierCaseInsensitive)
let defaultSchema = defaultSchemaName ? top.child(defaultSchemaName) : null
top.addNamespace(schema)
if (tables) (defaultSchema || top).addCompletions(tables)
Expand Down
4 changes: 4 additions & 0 deletions src/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export type SQLDialectSpec = {
/// The characters that can be used to quote identifiers. Defaults
/// to `"\""`.
identifierQuotes?: string
/// Controls whether identifiers are case-insensitive. Identifiers
/// with upper-case letters are quoted then set to false. Defaults to
/// false.
identifierCaseInsensitive?: boolean,
/// Controls whether bit values can be defined as 0b1010. Defaults
/// to false.
unquotedBitLiterals?: boolean,
Expand Down
2 changes: 2 additions & 0 deletions src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export interface Dialect {
operatorChars: string,
specialVar: string,
identifierQuotes: string,
identifierCaseInsensitive: boolean,
words: {[name: string]: number}
}

Expand All @@ -188,6 +189,7 @@ const defaults: Dialect = {
operatorChars: "*+\-%<>!=&|~^/",
specialVar: "?",
identifierQuotes: '"',
identifierCaseInsensitive: false,
words: keywords(SQLKeywords, SQLTypes)
}

Expand Down
11 changes: 10 additions & 1 deletion test/test-complete.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {EditorState} from "@codemirror/state"
import {CompletionContext, CompletionResult, CompletionSource} from "@codemirror/autocomplete"
import {schemaCompletionSource, PostgreSQL, MySQL, SQLConfig} from "@codemirror/lang-sql"
import {schemaCompletionSource, PostgreSQL, MySQL, SQLConfig, SQLDialect} from "@codemirror/lang-sql"
import ist from "ist"

function get(doc: string, conf: SQLConfig & {explicit?: boolean} = {}) {
Expand Down Expand Up @@ -162,6 +162,15 @@ describe("SQL completion", () => {
'`b c`, `b-c`, bup')
})

it("adds identifiers for upper case completions", () => {
ist(str(get("foo.c|", {schema: {foo: ["Column", "cell"]}, dialect: PostgreSQL})),
'"Column", cell')

const customDialect = SQLDialect.define({...PostgreSQL.spec, identifierCaseInsensitive: true})
ist(str(get("foo.c|", {schema: {foo: ["Column", "cell"]}, dialect: customDialect})),
'Column, cell')
})

it("supports nesting more than two deep", () => {
let s = {schema: {"one.two.three": ["four"]}}
ist(str(get("o|", s)), "one")
Expand Down