Skip to content

Commit

Permalink
Merge branch 'main' into fix-duplicated-columns
Browse files Browse the repository at this point in the history
  • Loading branch information
joacoc committed Nov 14, 2023
2 parents fbbe908 + 7afdb79 commit 730343e
Show file tree
Hide file tree
Showing 23 changed files with 1,283 additions and 695 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,8 @@ jobs:
sudo apt-get install -y libnss3-dev libasound2 libgdk-pixbuf2.0-dev libgtk-3-dev libxss-dev libatk1.0-0
- run: npm install
- run: xvfb-run -a npm test
if: runner.os == 'Linux'
- name: Run Tests on Linux
if: runner.os == 'Linux'
run: |
export MZ_CONFIG_PATH=$HOME/.config/materialize/test
xvfb-run -a npm test
5 changes: 4 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
"preLaunchTask": "${defaultBuildTask}",
"env": {
"MZ_CONFIG_PATH": "${env:HOME}/.config/materialize/test"
}
}
]
}
232 changes: 116 additions & 116 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@
"dark": "resources/clip_dark.svg"
},
"group": "inline"
},
{
"command": "materialize.copy",
"title": "Copy",
"when": "view == activityLog && viewItem == activityLogItem",
"icon": {
"light": "resources/clip_light.svg",
"dark": "resources/clip_dark.svg"
},
"group": "inline"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion resources/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,4 @@ vscode-text-field {
font-size: var(--vscode-font-size);
line-height: normal;
margin-bottom: 2px;
}
}
6 changes: 3 additions & 3 deletions src/clients/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default class AdminClient {

async getToken() {
// TODO: Expire should be at the half of the expiring time.
if (!this.auth || (new Date(this.auth.expires) > new Date())) {
if (!this.auth || (new Date(this.auth.expires) < new Date())) {
const authRequest: AuthenticationRequest = {
clientId: this.appPassword.clientId,
secret: this.appPassword.secretKey
Expand Down Expand Up @@ -67,7 +67,7 @@ export default class AdminClient {
}

/// Returns the JSON Web Key Set (JWKS) from the well known endpoint: `/.well-known/jwks.json`
async getJwks() {
private async getJwks() {
const client = jwksClient({
jwksUri: this.jwksEndpoint
});
Expand All @@ -78,7 +78,7 @@ export default class AdminClient {

/// Verifies the JWT signature using a JWK from the well-known endpoint and
/// returns the user claims.
async getClaims() {
private async getClaims() {
console.log("[AdminClient]", "Getting Token.");
const token = await this.getToken();

Expand Down
1 change: 0 additions & 1 deletion src/clients/cloud.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fetch from "node-fetch";
import AdminClient from "./admin";
import * as vscode from 'vscode';
import { Errors } from "../utilities/error";

const DEFAULT_API_CLOUD_ENDPOINT = 'https://api.cloud.materialize.com';
Expand Down
42 changes: 42 additions & 0 deletions src/clients/lsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ const SERVER_DECOMPRESS_PATH: string = path.join(os.tmpdir(), "mz", "bin", "mz-l
/// The final server binary path.
const SERVER_PATH: string = path.join(__dirname, "bin", "mz-lsp-server");


/// Represents the structure a client uses to understand
export interface ExecuteCommandParseStatement {
/// The sql content in the statement
sql: string,
/// The type of statement.
/// Represents the String version of [Statement].
kind: string,
}

/// Represents the response from the parse command.
interface ExecuteCommandParseResponse {
statements: Array<ExecuteCommandParseStatement>
}

/// This class implements the Language Server Protocol (LSP) client for Materialize.
/// The LSP is downloaded for an endpoint an it is out of the bundle. Binaries are heavy-weight
/// and is preferable to download on the first activation.
Expand Down Expand Up @@ -280,4 +295,31 @@ export default class LspClient {
stop() {
this.client && this.client.stop();
}

/**
* Sends a request to the LSP server to execute the parse command.
* The parse command returns the list of statements in an array,
* including their corresponding SQL and type (e.g., select, create_table, etc.).
*
* For more information about commands: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand
*/
async parseSql(sql: string): Promise<Array<ExecuteCommandParseStatement>> {
if (this.client) {
console.log("[LSP]", "Setting on request handler.");

// Setup the handler.
this.client.onRequest("workspace/executeCommand", (...params) => {
console.log("[LSP]", "Response params: ", params);
});

// Send request
const { statements } = await this.client.sendRequest("workspace/executeCommand", { command: "parse", arguments: [
sql
]}) as ExecuteCommandParseResponse;

return statements;
} else {
throw new Error("Client is not yet available.");
}
}
}
85 changes: 51 additions & 34 deletions src/clients/sql.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { Pool, QueryResult } from "pg";
import { Pool, PoolClient, PoolConfig, QueryResult } from "pg";
import AdminClient from "./admin";
import CloudClient from "./cloud";
import { Context, EventType } from "../context";
import { Profile } from "../context/config";
import AsyncContext from "../context/asyncContext";

export default class SqlClient {
private pool: Promise<Pool>;
private privateClient: Promise<PoolClient>;
private adminClient: AdminClient;
private cloudClient: CloudClient;
private context: Context;
private context: AsyncContext;
private profile: Profile;

constructor(
adminClient: AdminClient,
cloudClient: CloudClient,
profile: Profile,
context: Context,
context: AsyncContext,
) {
this.adminClient = adminClient;
this.cloudClient = cloudClient;
Expand All @@ -39,7 +40,20 @@ export default class SqlClient {
});
} catch (err) {
console.error("[SqlClient]", "Error creating pool: ", err);
this.context.emit("event", { type: EventType.error, message: err });
rej(err);
}
};

asyncOp();
});

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

Expand Down Expand Up @@ -74,7 +88,7 @@ export default class SqlClient {
return connectionOptions.join(" ");
}

private async buildPoolConfig() {
private async buildPoolConfig(): Promise<PoolConfig> {
console.log("[SqlClient]", "Loading host.");
const hostPromise = this.cloudClient?.getHost(this.profile.region);
console.log("[SqlClient]", "Loading user email.");
Expand All @@ -94,10 +108,19 @@ export default class SqlClient {
password: await this.context.getAppPassword(),
// Disable SSL for tests
ssl: (host && host.startsWith("localhost")) ? false : true,
keepAlive: true
};
}

async query(statement: string, values?: Array<any>): Promise<QueryResult<any>> {
/**
* Internal queries are intended for exploring cases.
* Like quering the catalog, or information about Materialize.
* Queries goes to the pool, and no client is kept.
* @param statement
* @param values
* @returns query results
*/
async internalQuery(statement: string, values?: Array<any>): Promise<QueryResult<any>> {
const pool = await this.pool;
// Row mode is a must.
// Otherwise when two columns have the same name, one is dropped
Expand All @@ -111,36 +134,30 @@ export default class SqlClient {
return results;
}

async* cursorQuery(statement: string): AsyncGenerator<QueryResult> {
const pool = await this.pool;
const client = await pool.connect();

try {
const batchSize = 100; // Number of rows to fetch in each batch

await client.query("BEGIN");
await client.query(`DECLARE c CURSOR FOR ${statement}`);
let finish = false;

// Run the query
while (!finish) {
let results: QueryResult = await client.query(`FETCH ${batchSize} c;`);
const { rowCount } = results;
/**
* Private queries are intended for the user. A private query reuses always the same client.
* In this way, it functions like a shell, processing one statement after another.
* @param statement
* @param values
* @returns query results
*/
async privateQuery(statement: string, values?: Array<any>): Promise<QueryResult<any>> {
const client = await this.privateClient;
const results = await client.query(statement, values);

if (rowCount === 0) {
finish = true;
}
return results;
}

yield results;
}
} finally {
try {
await client.query("COMMIT;");
} catch (err) {
console.error("[SqlClient]", "Error commiting transaction.", err);
}
// Release the client and pool resources
client.release();
/**
* Shut down cleanly the pool.
*/
async end() {
try {
const pool = await this.pool;
await pool.end();
} catch (err) {
console.error("[SqlClient]", "Error ending the pool: ", err);
}
}
}
Loading

0 comments on commit 730343e

Please sign in to comment.