Skip to content

Commit 493cb16

Browse files
authored
Merge branch 'release' into dependabot/npm_and_yarn/mysql2-3.9.8
2 parents 5774a0b + 151e1f3 commit 493cb16

File tree

17 files changed

+1794
-1373
lines changed

17 files changed

+1794
-1373
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module.exports = {
2323
'node/no-unsupported-features/es-syntax': 'off',
2424
'node/no-extraneous-import': 'off',
2525
"node/no-missing-import": ["error", {
26-
"allowModules": ["vscode"]
26+
"allowModules": ["vscode", "@deepnote/sqlint", "@deepnote/sql-parser", "@deepnote/sql-language-server", "sql-language-server"]
2727
}],
2828
'import/first': 0,
2929
'import/named': 2,

.github/workflows/cd.yml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: CD
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
publish:
12+
name: Publish to npm
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 10
15+
environment: release
16+
# Only run if the release tag follows the package-scoped pattern: @deepnote/package-name@version
17+
if: startsWith(github.event.release.tag_name, '@deepnote/')
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22+
23+
- name: Extract package info from tag
24+
id: package_info
25+
env:
26+
TAG: ${{ github.event.release.tag_name }}
27+
run: |
28+
# Extract package name and version from tag (format: @deepnote/package-name@version)
29+
if [[ ! "$TAG" =~ ^(@deepnote/[^@]+)@(.+)$ ]]; then
30+
echo "Error: Tag must follow format @deepnote/package-name@version"
31+
exit 1
32+
fi
33+
34+
PACKAGE_NAME="${BASH_REMATCH[1]}"
35+
TAG_VERSION="${BASH_REMATCH[2]}"
36+
37+
echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT
38+
echo "tag_version=$TAG_VERSION" >> $GITHUB_OUTPUT
39+
40+
echo "Detected package: $PACKAGE_NAME"
41+
echo "Detected version: $TAG_VERSION"
42+
43+
- name: Setup Node.js
44+
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
45+
with:
46+
node-version: '18'
47+
cache: 'yarn'
48+
registry-url: 'https://registry.npmjs.org/'
49+
50+
- name: Install dependencies
51+
run: yarn install --frozen-lockfile
52+
53+
- name: Validate package version
54+
env:
55+
PACKAGE_NAME: ${{ steps.package_info.outputs.package_name }}
56+
TAG_VERSION: ${{ steps.package_info.outputs.tag_version }}
57+
run: |
58+
# Map package name to directory
59+
case "$PACKAGE_NAME" in
60+
"@deepnote/sql-parser") PACKAGE_PATH="packages/sql-parser" ;;
61+
"@deepnote/sqlint") PACKAGE_PATH="packages/sqlint" ;;
62+
"@deepnote/sql-language-server") PACKAGE_PATH="packages/server" ;;
63+
*) echo "Error: Unknown package $PACKAGE_NAME"; exit 1 ;;
64+
esac
65+
66+
if [[ ! -f "$PACKAGE_PATH/package.json" ]]; then
67+
echo "Error: Package not found at $PACKAGE_PATH"
68+
exit 1
69+
fi
70+
71+
PACKAGE_VERSION=$(node -p "require('./$PACKAGE_PATH/package.json').version")
72+
73+
if [[ "$PACKAGE_VERSION" != "$TAG_VERSION" ]]; then
74+
echo "Error: Version mismatch!"
75+
echo " Tag version: $TAG_VERSION"
76+
echo " Package version: $PACKAGE_VERSION"
77+
exit 1
78+
fi
79+
80+
echo "Version validation passed: $TAG_VERSION"
81+
82+
- name: Build dependencies and package
83+
env:
84+
PACKAGE_NAME: ${{ steps.package_info.outputs.package_name }}
85+
run: |
86+
case "$PACKAGE_NAME" in
87+
"@deepnote/sql-parser")
88+
yarn workspace @deepnote/sql-parser build
89+
;;
90+
"@deepnote/sqlint")
91+
yarn workspace @deepnote/sql-parser build
92+
yarn build:sqlint
93+
;;
94+
"@deepnote/sql-language-server")
95+
yarn npm:prepublish
96+
;;
97+
esac
98+
99+
- name: Publish to npm
100+
env:
101+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
102+
PACKAGE_NAME: ${{ steps.package_info.outputs.package_name }}
103+
run: |
104+
case "$PACKAGE_NAME" in
105+
"@deepnote/sql-parser") cd packages/sql-parser ;;
106+
"@deepnote/sqlint") cd packages/sqlint ;;
107+
"@deepnote/sql-language-server") cd packages/server ;;
108+
esac
109+
npm publish --access public

.github/workflows/test.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ jobs:
55
runs-on: ubuntu-latest
66
steps:
77
- uses: actions/checkout@v2
8-
- uses: actions/setup-node@v2
8+
- uses: actions/setup-node@v4
99
with:
10-
node-version: 14.x
10+
node-version: 20.x
1111
- uses: actions/setup-python@v2
1212
with:
1313
python-version: 3.8.12

packages/server/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "sql-language-server",
3-
"version": "1.7.0",
2+
"name": "@deepnote/sql-language-server",
3+
"version": "2.0.2",
44
"main": "dist/src/index.js",
55
"bin": {
66
"sql-language-server": "./npm_bin/cli.js"
@@ -39,21 +39,21 @@
3939
"LICENSE"
4040
],
4141
"dependencies": {
42+
"@deepnote/sql-parser": "^2.0.0",
43+
"@deepnote/sqlint": "^2.0.0",
4244
"@google-cloud/bigquery": "^5.9.0",
43-
"@deepnote/sql-parser": "^1.7.0",
4445
"@types/pg": "^8.6.6",
4546
"@types/yargs": "^17.0.8",
4647
"cardinal": "^2.1.1",
4748
"jest": "^26.0.1",
4849
"mysql2": "^3.9.8",
4950
"node-ssh-forward": "^0.6.3",
5051
"pg": "^8.9.0",
51-
"sqlint": "^1.7.0",
5252
"sqlite3": "^5.0.3",
5353
"vscode-languageclient": "^6.1.3",
54-
"vscode-languageserver": "8.0.0-next.8",
5554
"vscode-languageserver-protocol": "^3.15.3",
5655
"vscode-languageserver-textdocument": "^1.0.1",
56+
"vscode-languageserver": "8.0.0-next.8",
5757
"yargs": "^17.3.1"
5858
},
5959
"devDependencies": {
@@ -69,4 +69,4 @@
6969
"tslib": "^2.3.1",
7070
"typescript": "^4.4.2"
7171
}
72-
}
72+
}

packages/server/src/cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Diagnostic as SQLintDiagnostic } from 'sqlint'
1+
import { Diagnostic as SQLintDiagnostic } from '@deepnote/sqlint'
22
import { Diagnostic, Range } from 'vscode-languageserver'
33
import { stubLogger } from './logger'
44

packages/server/src/complete/candidates/createTableCandidates.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ export function createCatalogDatabaseAndTableCandidates(
2727
const qualificationLevel = lastToken.split('.').length - 1
2828

2929
const qualifiedEntities = tables.flatMap((table) => {
30+
const results: Identifier[] = []
31+
32+
// When user types without dots (qualificationLevel === 0), always include
33+
// a table name suggestion. This allows typing "act" to match "actor" even
34+
// if the table has a database (e.g., "squeal.actor").
35+
if (qualificationLevel === 0) {
36+
const tableIdentifier = new Identifier(
37+
lastToken,
38+
table.tableName,
39+
'',
40+
ICONS.TABLE,
41+
onFromClause ? 'FROM' : 'OTHERS'
42+
)
43+
results.push(tableIdentifier)
44+
}
45+
46+
// Also add qualified suggestions (catalog/database) based on qualification level
3047
let qualificationNeeded = 0
3148
if (table.catalog) {
3249
qualificationNeeded++
@@ -37,14 +54,18 @@ export function createCatalogDatabaseAndTableCandidates(
3754
const qualificationLevelNeeded = qualificationNeeded - qualificationLevel
3855
switch (qualificationLevelNeeded) {
3956
case 0: {
40-
const tableIdentifier = new Identifier(
41-
lastToken,
42-
getFullyQualifiedTableName(table),
43-
'',
44-
ICONS.TABLE,
45-
onFromClause ? 'FROM' : 'OTHERS'
46-
)
47-
return [tableIdentifier]
57+
// Only add fully qualified name if we haven't already added just the table name
58+
if (qualificationLevel > 0) {
59+
const tableIdentifier = new Identifier(
60+
lastToken,
61+
getFullyQualifiedTableName(table),
62+
'',
63+
ICONS.TABLE,
64+
onFromClause ? 'FROM' : 'OTHERS'
65+
)
66+
results.push(tableIdentifier)
67+
}
68+
break
4869
}
4970
case 1: {
5071
const qualifiedDatabaseName =
@@ -60,7 +81,7 @@ export function createCatalogDatabaseAndTableCandidates(
6081
ICONS.DATABASE,
6182
onFromClause ? 'FROM' : 'OTHERS'
6283
)
63-
return [databaseIdentifier]
84+
results.push(databaseIdentifier)
6485
}
6586
break
6687
}
@@ -73,11 +94,11 @@ export function createCatalogDatabaseAndTableCandidates(
7394
ICONS.CATALOG,
7495
onFromClause ? 'FROM' : 'OTHERS'
7596
)
76-
return [catalogIdentifier]
97+
results.push(catalogIdentifier)
7798
}
7899
break
79100
}
80-
return []
101+
return results
81102
})
82103

83104
return qualifiedEntities

packages/server/src/complete/complete.ts

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class Completer {
111111
// Incomplete sub query 'SELECT sub FROM (SELECT e. FROM employees e) sub'
112112
this.addCandidatesForIncompleteSubquery(fromNodeOnCursor)
113113
} else {
114-
this.addCandidatesForSelectQuery(e, fromNodes)
114+
this.addCandidatesForSelectQuery(e, fromNodes, parsedFromClause)
115115
const expectedLiteralNodes =
116116
e.expected?.filter(
117117
(v): v is ExpectedLiteralNode => v.type === 'literal'
@@ -239,7 +239,11 @@ class Completer {
239239
this.addCandidatesForTables(this.schema.tables, false)
240240
}
241241

242-
addCandidatesForSelectQuery(e: ParseError, fromNodes: FromTableNode[]) {
242+
addCandidatesForSelectQuery(
243+
e: ParseError,
244+
fromNodes: FromTableNode[],
245+
parsedFromClause: FromClauseParserResult
246+
) {
243247
const subqueryTables = createTablesFromFromNodes(fromNodes)
244248
const schemaAndSubqueries = this.schema.tables.concat(subqueryTables)
245249
this.addCandidatesForSelectStar(fromNodes, schemaAndSubqueries)
@@ -251,20 +255,38 @@ class Completer {
251255
this.addCandidatesForExpectedLiterals(expectedLiteralNodes)
252256
this.addCandidatesForFunctions()
253257

254-
const { addedSome: addedSomeScopedColumnCandidates } =
255-
this.addCandidatesForScopedColumns(fromNodes, schemaAndSubqueries)
256-
if (!addedSomeScopedColumnCandidates) {
257-
this.addCandidatesForUnscopedColumns(fromNodes, schemaAndSubqueries)
258-
}
259-
260-
this.addCandidatesForAliases(fromNodes)
261-
258+
// Detect FROM clause context BEFORE adding column suggestions
262259
const fromNodesContainingCursor = fromNodes.filter((tableNode) =>
263260
isPosInLocation(tableNode.location, this.pos)
264261
)
265262
const isCursorInsideFromClause = fromNodesContainingCursor.length > 0
266-
if (isCursorInsideFromClause) {
267-
// only add table candidates if the cursor is inside a FROM clause or JOIN clause, etc.
263+
264+
// Check if cursor is right after FROM keyword or typing a table name
265+
const afterFromClause = parsedFromClause.after?.trim().toUpperCase() || ''
266+
const isCursorAfterFromKeyword =
267+
afterFromClause === 'FROM' ||
268+
afterFromClause.startsWith('FROM ') ||
269+
/^(INNER |LEFT |RIGHT |FULL |FULL OUTER |CROSS |NATURAL |OUTER )?JOIN( |$)/.test(
270+
afterFromClause
271+
)
272+
273+
const isTypingTableName =
274+
isCursorInsideFromClause || isCursorAfterFromKeyword
275+
276+
if (!isTypingTableName) {
277+
const { addedSome: addedSomeScopedColumnCandidates } =
278+
this.addCandidatesForScopedColumns(fromNodes, schemaAndSubqueries)
279+
280+
if (!addedSomeScopedColumnCandidates) {
281+
this.addCandidatesForUnscopedColumns(fromNodes, schemaAndSubqueries)
282+
}
283+
}
284+
285+
this.addCandidatesForAliases(fromNodes)
286+
287+
if (isTypingTableName) {
288+
// add table candidates if the cursor is inside a FROM clause, JOIN clause,
289+
// or right after FROM/JOIN keyword waiting for a table name
268290
this.addCandidatesForTables(schemaAndSubqueries, true)
269291
}
270292

@@ -328,14 +350,41 @@ class Completer {
328350
if (!ast.distinct) {
329351
this.addCandidate(toCompletionItemForKeyword('DISTINCT'))
330352
}
353+
354+
// Check if cursor is inside a FROM clause table reference
355+
// This handles the case where "SELECT * FROM a" parses successfully
356+
// but we still want to suggest tables starting with "a"
357+
const parsedFromClause = getFromNodesFromClause(this.sql)
358+
const fromNodes = getAllNestedFromNodes(
359+
parsedFromClause?.from?.tables || []
360+
)
361+
const subqueryTables = createTablesFromFromNodes(fromNodes)
362+
const schemaAndSubqueries = this.schema.tables.concat(subqueryTables)
363+
364+
for (const tableNode of fromNodes) {
365+
if (tableNode.type === 'table') {
366+
// Check if the lastToken matches the table name (user is typing the table name)
367+
// This means the cursor is ON the table name, not after it (like typing an alias)
368+
const tableNameMatches =
369+
this.lastToken.length > 0 &&
370+
tableNode.table.toLowerCase().startsWith(this.lastToken.toLowerCase())
371+
372+
if (tableNameMatches && isPosInLocation(tableNode.location, this.pos)) {
373+
// Cursor is typing a table name - suggest tables
374+
this.addCandidatesForTables(schemaAndSubqueries, true)
375+
if (logger.isDebugEnabled())
376+
logger.debug(
377+
`parse query returns: ${JSON.stringify(this.candidates)}`
378+
)
379+
return
380+
}
381+
}
382+
}
383+
331384
const columnRef = findColumnAtPosition(ast, this.pos)
332385
if (!columnRef) {
333386
this.addJoinCondidates(ast)
334387
} else {
335-
const parsedFromClause = getFromNodesFromClause(this.sql)
336-
const fromNodes = parsedFromClause?.from?.tables || []
337-
const subqueryTables = createTablesFromFromNodes(fromNodes)
338-
const schemaAndSubqueries = this.schema.tables.concat(subqueryTables)
339388
if (columnRef.table) {
340389
// We know what table/alias this column belongs to
341390
// Find the corresponding table and suggest it's columns

packages/server/src/createDiagnostics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { parse, ParseError } from '@deepnote/sql-parser'
22
import { PublishDiagnosticsParams, Diagnostic } from 'vscode-languageserver'
33
import { DiagnosticSeverity } from 'vscode-languageserver-types'
4-
import { lint, ErrorLevel, LintResult, RawConfig } from 'sqlint'
4+
import { lint, ErrorLevel, LintResult, RawConfig } from '@deepnote/sqlint'
55
import cache, { LintCache } from './cache'
66
import { stubLogger } from './logger'
77

packages/server/src/createServer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import {
1717
Position,
1818
CodeActionKind,
1919
} from 'vscode-languageserver-types'
20-
import { lint, LintResult } from 'sqlint'
21-
import { RawConfig } from 'sqlint'
20+
import { lint, LintResult, FixDescription } from '@deepnote/sqlint'
21+
import { RawConfig } from '@deepnote/sqlint'
2222
import cache from './cache'
2323
import { complete } from './complete'
2424
import createDiagnostics from './createDiagnostics'
@@ -291,7 +291,7 @@ export function createServerWithConnection(connection: Connection) {
291291
documentChanges: [
292292
TextDocumentEdit.create(
293293
{ uri: params.textDocument.uri, version: document.version },
294-
fixes.map((v) => {
294+
fixes.map((v: FixDescription) => {
295295
const edit =
296296
v.range.startOffset === v.range.endOffset
297297
? TextEdit.insert(

0 commit comments

Comments
 (0)