Skip to content

Commit

Permalink
Merge branch 'main' into lsp-schema-completion
Browse files Browse the repository at this point in the history
  • Loading branch information
joacoc committed Dec 7, 2023
2 parents 6697cb4 + e1958c1 commit b493edd
Show file tree
Hide file tree
Showing 18 changed files with 193 additions and 118 deletions.
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@
},
"languages": [
{
"id": "materialize-sql",
"id": "mzsql",
"extensions": [
".sql"
".sql",
".mzsql"
],
"aliases": [
"Materialize SQL"
Expand All @@ -56,9 +57,9 @@
],
"grammars": [
{
"language": "materialize-sql",
"scopeName": "source.materialize-sql",
"path": "./syntaxes/materialize-sql.tmLanguage"
"language": "mzsql",
"scopeName": "source.mzsql",
"path": "./syntaxes/mzsql.tmLanguage"
}
],
"menus": {
Expand Down Expand Up @@ -186,7 +187,7 @@
"command": "materialize.run",
"key": "ctrl+enter",
"mac": "cmd+enter",
"when": "resourceLangId == 'sql' || resourceLangId == 'materialize-sql'"
"when": "resourceLangId == 'sql' || resourceLangId == 'mzsql'"
}
]
},
Expand Down
6 changes: 3 additions & 3 deletions src/clients/admin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fetch from "node-fetch";
import AppPassword from "../context/appPassword";
import { Errors, ExtensionError } from "../utilities/error";
import jwksClient from "jwks-rsa";
import { verify } from "node-jsonwebtoken";
import { fetchWithRetry } from "../utilities/utils";

interface AuthenticationResponse {
accessToken: string,
Expand Down Expand Up @@ -44,11 +44,11 @@ export default class AdminClient {
secret: this.appPassword.secretKey
};

const response = await fetch(this.adminEndpoint, {
const response = await fetchWithRetry(this.adminEndpoint, {
method: 'post',
body: JSON.stringify(authRequest),
// eslint-disable-next-line @typescript-eslint/naming-convention
headers: {'Content-Type': 'application/json'}
headers: {'Content-Type': 'application/json' }
});

if (response.status === 200) {
Expand Down
4 changes: 2 additions & 2 deletions src/clients/cloud.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch from "node-fetch";
import AdminClient from "./admin";
import { Errors, ExtensionError } from "../utilities/error";
import { fetchWithRetry } from "../utilities/utils";

const DEFAULT_API_CLOUD_ENDPOINT = 'https://api.cloud.materialize.com';

Expand Down Expand Up @@ -58,7 +58,7 @@ export default class CloudClient {
}

async fetch(endpoint: string) {
return fetch(endpoint, {
return fetchWithRetry(endpoint, {
method: 'get',
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down
8 changes: 4 additions & 4 deletions src/clients/lsp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import fetch from "node-fetch";
import path from "path";
import * as vscode from "vscode";
import {
Expand All @@ -17,6 +16,7 @@ import { SemVer } from "semver";
import { Errors, ExtensionError } from "../utilities/error";
import { ExplorerSchema } from "../context/context";
import * as Sentry from "@sentry/node";
import { fetchWithRetry } from "../utilities/utils";

// This endpoint returns a string with the latest LSP version.
const BINARIES_ENDPOINT = "https://binaries.materialize.com";
Expand Down Expand Up @@ -206,7 +206,7 @@ export default class LspClient {
*/
private async fetchLatestVersionNumber() {
console.log("[LSP]", "Fetching latest version number.");
const response = await fetch(LATEST_VERSION_ENDPOINT);
const response = await fetchWithRetry(LATEST_VERSION_ENDPOINT);
const latestVersion: string = await response.text();

return new SemVer(latestVersion);
Expand All @@ -221,7 +221,7 @@ export default class LspClient {
const endpoint = this.getEndpointByOs(latestVersion);

console.log("[LSP]", `Fetching LSP from: ${endpoint}`);
const binaryResponse = await fetch(endpoint);
const binaryResponse = await fetchWithRetry(endpoint);
const buffer = await binaryResponse.arrayBuffer();

return buffer;
Expand Down Expand Up @@ -271,7 +271,7 @@ export default class LspClient {
console.log("[LSP]", "Schema: ", this.schema);

const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "materialize-sql"}],
documentSelector: [{ scheme: "file", language: "mzsql"}],
initializationOptions: {
formattingWidth,
schema: this.schema,
Expand Down
98 changes: 58 additions & 40 deletions src/clients/sql.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Pool, PoolClient, PoolConfig, QueryArrayResult, QueryResult } from "pg";
import { Pool, PoolClient, PoolConfig, QueryArrayResult } from "pg";
import AdminClient from "./admin";
import CloudClient from "./cloud";
import { Profile } from "../context/config";
import AsyncContext from "../context/asyncContext";
import { Errors, ExtensionError } from "../utilities/error";

export default class SqlClient {
private pool: Promise<Pool>;
Expand All @@ -12,6 +11,7 @@ export default class SqlClient {
private cloudClient: CloudClient;
private context: AsyncContext;
private profile: Profile;
private ended: boolean;

constructor(
adminClient: AdminClient,
Expand All @@ -23,45 +23,61 @@ export default class SqlClient {
this.cloudClient = cloudClient;
this.profile = profile;
this.context = context;
this.ended = false;
this.pool = this.buildPool();
this.privateClient = this.buildPrivateClient();
this.handleReconnection();
}

this.pool = new Promise((res, rej) => {
const asyncOp = async () => {
try {
console.log("[SqlClient]", "Building config.");
const config = await this.buildPoolConfig();
const pool = new Pool(config);
console.log("[SqlClient]", "Connecting pool.");

pool.connect().then(() => {
console.log("[SqlClient]", "Pool successfully connected.");
res(pool);
}).catch((err) => {
console.error(err);
rej(new ExtensionError(Errors.poolConnectionFailure, err));
});
} catch (err) {
console.error("[SqlClient]", "Error creating pool: ", err);
rej(new ExtensionError(Errors.poolCreationFailure, err));
}
};

asyncOp();
});
private async handleReconnection() {
let reconnecting = false;

const reconnect = (err: Error) => {
console.error("[SqlClient]", "Unexpected error: ", err);
console.log("[SqlClient]", "Reconnecting.");
if (reconnecting === false && this.ended === false) {
reconnecting = true;
const interval = setInterval(async () => {
try {
const pool = await this.pool;
pool.end();
} catch (err) {
console.error("[SqlClient]", "Error awaiting pool to end. It is ok it the pool connection failed.");
} finally {
this.pool = this.buildPool();
this.privateClient = this.buildPrivateClient();
this.handleReconnection();
reconnecting = false;
clearInterval(interval);
}
}, 5000);
}
};

this.privateClient = new Promise((res, rej) => {
const asyncOp = async () => {
try {
const pool = await this.pool;
const client = await pool.connect();
res(client);
} catch (err) {
console.error("[SqlClient]", "Error awaiting the pool: ", err);
rej(err);
}
};

asyncOp();
});
try {
const pool = await this.pool;
pool.on("error", reconnect);

try {
const client = await this.privateClient;
client.on("error", reconnect);
} catch (err) {
reconnect(err as Error);
console.error("[SqlClient]", "Unexpected error on client: ", err);
}
} catch (err) {
reconnect(err as Error);
console.error("[SqlClient]", "Unexpected error on pool: ", err);
}
}

private async buildPrivateClient(): Promise<PoolClient> {
const pool = await this.pool;
return pool.connect();
}

private async buildPool(): Promise<Pool> {
return new Pool(await this.buildPoolConfig());
}

async connectErr() {
Expand Down Expand Up @@ -124,7 +140,7 @@ export default class SqlClient {
* @param values
* @returns query results
*/
async internalQuery(statement: string, values?: Array<any>): Promise<QueryArrayResult<any>> {
async internalQuery(statement: string, values?: Array<string | number>): Promise<QueryArrayResult<any>> {

Check warning on line 143 in src/clients/sql.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unexpected any. Specify a different type
const pool = await this.pool;
const results = await pool.query(statement, values);

Expand Down Expand Up @@ -163,6 +179,8 @@ export default class SqlClient {
* Shut down cleanly the pool.
*/
async end() {
this.ended = true;

try {
const pool = await this.pool;
await pool.end();
Expand Down
2 changes: 1 addition & 1 deletion src/context/appPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ export default class AppPassword {
}
}

throw new ExtensionError(Errors.invalidAppPassword, "Invalid length.");
throw new ExtensionError(Errors.invalidAppPassword, "Invalid app-password length.");
}
}
13 changes: 10 additions & 3 deletions src/context/asyncContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,16 @@ export default class AsyncContext extends Context {
*/
async removeAndSaveProfile(name: string) {
try {
this.config.removeAndSaveProfile(name);
const success = await this.reloadContext();
return success;
await this.config.removeAndSaveProfile(name);
const leftProfileNames = this.getProfileNames();
console.log("[AsyncContext]", "Left profile names: ", leftProfileNames);
if (leftProfileNames && leftProfileNames.length > 0) {
const success = await this.reloadContext();
return success;
} else {
this.isReadyPromise = new Promise((res) => res(true));
return true;
}
} catch (err) {
throw this.parseErr(err, "Error reloading context.");
}
Expand Down
19 changes: 18 additions & 1 deletion src/context/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export class Config {
}

this.save();
await this.removeKeychainPassword(name);

if (newProfileName) {
this.setProfile(newProfileName);
Expand Down Expand Up @@ -276,7 +277,6 @@ export class Config {
resolve();
}
};

keychain.setPassword({
account,
password: appPassword,
Expand All @@ -286,6 +286,23 @@ export class Config {
});
}

/// Removes an app-password from the keychain
async removeKeychainPassword(account: string): Promise<void> {
return new Promise((resolve, reject) => {
const cb = (err: KeychainError): void => {
if (err) {
reject(err);
} else {
resolve();
}
};
keychain.deletePassword({
account,
service: KEYCHAIN_SERVICE,
}, cb);
});
}

/// Gets a keychain app-password for a profile
async getKeychainPassword(account: string): Promise<string> {
return new Promise((resolve, reject) => {
Expand Down
5 changes: 3 additions & 2 deletions src/providers/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ class ActivityLogItem extends vscode.TreeItem {
constructor(public readonly log: ActivityLog) {
// Shorten the displayed query if it's too long
const shortSQL = log.sql.length > 50 ? log.sql.substring(0, 50) + "..." : log.sql;

super(shortSQL, vscode.TreeItemCollapsibleState.None);
// Removes enters and extra spaces.
const cleanSQL = shortSQL.replace(/\n/g, '').replace(/\s{2,}/g, ' ');
super(cleanSQL, vscode.TreeItemCollapsibleState.None);

// Set iconPath based on the status
const iconName = log.status === "success" ? "success_icon.svg" : "error_icon.svg";
Expand Down
3 changes: 1 addition & 2 deletions src/providers/auth.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as vscode from "vscode";
import express, { Request, Response, Application, } from 'express';
import { getUri } from "../utilities/getUri";
import { getNonce, getUri } from "../utilities/utils";
import AppPassword from "../context/appPassword";
import { getNonce } from "../utilities/getNonce";
import AsyncContext from "../context/asyncContext";
import { Errors, ExtensionError } from "../utilities/error";

Expand Down
3 changes: 1 addition & 2 deletions src/providers/results.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as vscode from "vscode";
import { getUri } from "../utilities/getUri";
import { getNonce } from "../utilities/getNonce";
import { getNonce, getUri } from "../utilities/utils";
import { QueryResult } from "pg";

interface Results extends QueryResult {
Expand Down
5 changes: 3 additions & 2 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,14 @@ suite('Extension Test Suite', () => {
let err = false;
try {
await context.setProfile("invalid_profile");
} catch (error) {
} catch (error: any) {
assert.ok(error.message === "Invalid authentication");
err = true;
}

assert.ok(err);

}).timeout(10000);
}).timeout(30000);

test('Alternative parser', async () => {
const lsp = new LspClient();
Expand Down
4 changes: 2 additions & 2 deletions src/test/suite/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ export function mockServer(): Promise<string> {
});

return new Promise((res) => {
app.listen(3000, 'localhost', () => {
console.log(`Mock server listening at localhost:3000`);
const server = app.listen(3000, 'localhost', () => {
console.log(`Mock server listening at localhost:3000: `, server.listening);
res("Loaded.");
});
});
Expand Down
6 changes: 5 additions & 1 deletion src/utilities/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,9 @@ export enum Errors {
/**
* Raises when there is an issue restarting the LSP client.
*/
lspRestartFailure = "Error restarting the LSP client."
lspRestartFailure = "Error restarting the LSP client.",
/**
* Raises when a fetch failes after a minute.
*/
fetchTimeoutError = "Failed to fetch after a minute."
}
Loading

0 comments on commit b493edd

Please sign in to comment.