Skip to content
Open
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
104 changes: 15 additions & 89 deletions crates/pgls_workspace/src/features/code_actions.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
//! The code actions offered at the cursor, and the commands and fixes they run: lint fixes and
//! suppressions (`lint`), expanding a `*` into its columns (`expand_star`), executing the
//! statement (`execute_statement`), and invalidating the schema cache (`schema_cache`).

use crate::workspace::StatementId;
use pgls_analyse::{ActionCategory, Applicability};
use pgls_configuration::RuleSelector;
use pgls_fs::PgLSPath;
use pgls_text_size::{TextRange, TextSize};

pub(crate) mod execute_statement;
pub(crate) mod expand_star;
pub(crate) mod lint;
pub(crate) mod schema_cache;

pub use execute_statement::{
EXECUTE_STATEMENT_MAX_ROWS, ExecuteStatementError, ExecuteStatementParams,
ExecuteStatementResult,
};
pub use lint::{FixFileMode, FixFileParams, FixFileResult};

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CodeActionsParams {
Expand Down Expand Up @@ -67,92 +82,3 @@ pub enum CommandActionCategory {
ExecuteStatement(StatementId),
InvalidateSchemaCache,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ExecuteStatementParams {
pub project_key: crate::workspace::ProjectKey,
pub statement_id: StatementId,
pub path: PgLSPath,
}

/// The most rows an executed statement returns; the rest are dropped and the result is marked
/// as truncated.
pub const EXECUTE_STATEMENT_MAX_ROWS: usize = 100;

/// The outcome of executing a statement.
///
/// A statement that returns rows (`select`, `values`, `returning`, `show`, `explain`, ...) fills
/// `columns` and `rows`. Any other statement sets `rows_affected` instead.
#[derive(Debug, serde::Serialize, serde::Deserialize, Default, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct ExecuteStatementResult {
/// A short summary for the user.
pub message: String,
/// The names of the returned columns.
#[serde(default)]
pub columns: Vec<String>,
/// The returned rows, at most [`EXECUTE_STATEMENT_MAX_ROWS`]. Each value is in its Postgres
/// text representation, as `psql` shows it, and `null` for SQL `NULL`.
#[serde(default)]
pub rows: Vec<Vec<Option<String>>>,
/// Whether the statement returned more rows than `rows` holds.
#[serde(default)]
pub truncated: bool,
/// The number of rows affected, for a statement that returns no rows.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rows_affected: Option<u64>,
/// Why Postgres rejected the statement, if it did.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<ExecuteStatementError>,
}

/// An error that Postgres raised while executing a statement.
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct ExecuteStatementError {
/// The primary error message, e.g. `division by zero`.
pub message: String,
/// The SQLSTATE error code, e.g. `22012`.
pub code: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,
/// The offset of the error in the document, if Postgres reported a position.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub position: Option<TextSize>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct FixFileParams {
pub project_key: crate::workspace::ProjectKey,
pub path: PgLSPath,
pub fix_file_mode: FixFileMode,
pub only: Vec<RuleSelector>,
pub skip: Vec<RuleSelector>,
}

/// Which fixes `fix_file` applies.
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum FixFileMode {
/// Only the fixes that are safe to apply.
SafeFixes,
/// The safe and the unsafe fixes.
SafeAndUnsafeFixes,
}

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct FixFileResult {
/// The content of the file with the fixes applied.
pub code: String,
/// The number of fixes that were applied.
pub fixes_applied: u32,
/// The number of unsafe fixes that were not applied with [FixFileMode::SafeFixes].
pub skipped_suggested_fixes: u32,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! The command that executes the statement at the cursor against the database.

use pgls_fs::PgLSPath;
use pgls_text_size::TextSize;

use super::{CodeAction, CodeActionKind, CommandAction, CommandActionCategory};
use crate::workspace::{DefaultMapper, Document, StatementId};

#[cfg(feature = "db")]
pub(crate) mod execution;

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ExecuteStatementParams {
pub project_key: crate::workspace::ProjectKey,
pub statement_id: StatementId,
pub path: PgLSPath,
}

/// The most rows an executed statement returns; the rest are dropped and the result is marked
/// as truncated.
pub const EXECUTE_STATEMENT_MAX_ROWS: usize = 100;

/// The outcome of executing a statement.
///
/// A statement that returns rows (`select`, `values`, `returning`, `show`, `explain`, ...) fills
/// `columns` and `rows`. Any other statement sets `rows_affected` instead.
#[derive(Debug, serde::Serialize, serde::Deserialize, Default, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct ExecuteStatementResult {
/// A short summary for the user.
pub message: String,
/// The names of the returned columns.
#[serde(default)]
pub columns: Vec<String>,
/// The returned rows, at most [`EXECUTE_STATEMENT_MAX_ROWS`]. Each value is in its Postgres
/// text representation, as `psql` shows it, and `null` for SQL `NULL`.
#[serde(default)]
pub rows: Vec<Vec<Option<String>>>,
/// Whether the statement returned more rows than `rows` holds.
#[serde(default)]
pub truncated: bool,
/// The number of rows affected, for a statement that returns no rows.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rows_affected: Option<u64>,
/// Why Postgres rejected the statement, if it did.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<ExecuteStatementError>,
}

/// An error that Postgres raised while executing a statement.
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct ExecuteStatementError {
/// The primary error message, e.g. `division by zero`.
pub message: String,
/// The SQLSTATE error code, e.g. `22012`.
pub code: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,
/// The offset of the error in the document, if Postgres reported a position.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub position: Option<TextSize>,
}

/// The actions that execute the statements at `cursor`. `disabled_reason` says why statement
/// execution isn't allowed, if it isn't.
pub(crate) fn execute_statement_actions(
doc: &Document,
cursor: TextSize,
disabled_reason: Option<String>,
) -> Vec<CodeAction> {
doc.iter_at(DefaultMapper, cursor)
.map(|(stmt, _, txt)| {
let title = format!(
"Execute Statement: {}...",
txt.chars().take(50).collect::<String>()
);

CodeAction {
title,
kind: CodeActionKind::Command(CommandAction {
category: CommandActionCategory::ExecuteStatement(stmt),
}),
disabled_reason: disabled_reason.clone(),
}
})
.collect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ use sqlx::{
postgres::{PgColumn, PgDatabaseError, PgErrorPosition, PgRow},
};

use super::{EXECUTE_STATEMENT_MAX_ROWS, ExecuteStatementError, ExecuteStatementResult};
use crate::diagnostics::is_connection_failure;
use crate::features::code_actions::{
EXECUTE_STATEMENT_MAX_ROWS, ExecuteStatementError, ExecuteStatementResult,
};

/// Executes `sql` and collects the rows it returns, if any.
///
Expand All @@ -18,7 +16,7 @@ use crate::features::code_actions::{
///
/// Each statement runs on its own: afterwards, the session is reset, so a `begin` or `set` does
/// not carry over to the next statement on the connection.
pub(super) async fn execute_statement(
pub(crate) async fn execute_statement(
pool: &PgPool,
sql: &str,
) -> Result<ExecuteStatementResult, sqlx::Error> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::features::code_actions::{CodeAction, CodeActionKind, EditAction, Text

/// The action that replaces the `*` or `t.*` of `reference` with its columns. `content` is the
/// text of the file, and the statement of the reference starts at `stmt_start`.
pub(super) fn expand_star_action(
pub(crate) fn expand_star_action(
reference: &Reference,
content: &str,
stmt_start: TextSize,
Expand Down Expand Up @@ -44,7 +44,7 @@ pub(super) fn expand_star_action(
/// `select *` or `select u.*`. The line of the cursor is scanned on its own: `*` in comments
/// and strings, `count(*)`, and multiplications don't count. If the line doesn't scan, like
/// a line inside a multi-line string, any `*` on it counts.
pub(super) fn is_at_star(content: &str, position: TextSize) -> bool {
pub(crate) fn is_at_star(content: &str, position: TextSize) -> bool {
let offset = usize::from(position).min(content.len());
let line_start = content[..offset].rfind('\n').map_or(0, |i| i + 1);
let line_end = content[offset..]
Expand Down
Loading
Loading