Skip to content

[pull] main from supabase-community:main #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 25, 2025
Merged
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
45 changes: 30 additions & 15 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -33,8 +33,10 @@ jobs:
steps:
- name: Checkout PR branch
uses: actions/checkout@v4

- name: Free Disk Space
uses: ./.github/actions/free-disk-space

- name: Install toolchain
uses: moonrepo/setup-rust@v1
with:
@@ -43,15 +45,23 @@ jobs:
cache-base: main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest

- name: Setup Bun
uses: oven-sh/setup-bun@v2

- name: Install JS dependencies
run: bun install

- name: Setup Just
uses: extractions/setup-just@v3

- name: Echo Tool Versions
run: |
just format-ci-versions

- name: Run format
run: |
cargo fmt --all --check
taplo format --check
biome format
just format-ci

actionlint:
name: Lint GitHub Actions
@@ -96,17 +106,22 @@ jobs:
- name: Setup sqlx-cli
run: cargo install sqlx-cli

- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Setup Bun
uses: oven-sh/setup-bun@v2

- name: Install JS dependencies
run: bun install

- name: Setup Just
uses: extractions/setup-just@v3

- name: Echo Tool Versions
run: |
just lint-ci-versions

- name: Run Lints
run: |
cargo sqlx prepare --check --workspace
cargo clippy --fix
cargo run -p rules_check
biome lint --write
just lint-ci

- name: Check for changes
run: |
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ homepage = "https://supabase.com/"
keywords = ["linter", "typechecker", "postgres", "language-server"]
license = "MIT"
repository = "https://github.com/supabase-community/postgres-language-server"
rust-version = "1.85.0"
rust-version = "1.86.0"

[workspace.dependencies]
# supporting crates unrelated to postgres
4 changes: 0 additions & 4 deletions crates/pgt_cli/src/cli_options.rs
Original file line number Diff line number Diff line change
@@ -18,10 +18,6 @@ pub struct CliOptions {
#[bpaf(long("use-server"), switch, fallback(false))]
pub use_server: bool,

/// Skip connecting to the database and only run checks that don't require a database connection.
#[bpaf(long("skip-db"), switch, fallback(false))]
pub skip_db: bool,

/// Print additional diagnostics, and some diagnostics show more information. Also, print out what files were processed and which ones were modified.
#[bpaf(long("verbose"), switch, fallback(false))]
pub verbose: bool,
1 change: 0 additions & 1 deletion crates/pgt_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -307,7 +307,6 @@ pub(crate) trait CommandRunner: Sized {
configuration,
vcs_base_path,
gitignore_matches,
skip_db: cli_options.skip_db,
})?;

let execution = self.get_execution(cli_options, console, workspace)?;
4 changes: 1 addition & 3 deletions crates/pgt_completions/src/providers/helper.rs
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@ pub(crate) fn get_completion_text_with_schema(
item_name: &str,
item_schema_name: &str,
) -> Option<CompletionText> {
if item_schema_name == "public" {
None
} else if ctx.schema_name.is_some() {
if item_schema_name == "public" || ctx.schema_name.is_some() {
None
} else {
let node = ctx.node_under_cursor.unwrap();
36 changes: 18 additions & 18 deletions crates/pgt_completions/src/sanitization.rs
Original file line number Diff line number Diff line change
@@ -219,19 +219,19 @@ mod tests {
.set_language(tree_sitter_sql::language())
.expect("Error loading sql language");

let mut tree = parser.parse(input, None).unwrap();
let tree = parser.parse(input, None).unwrap();

// select | from users; <-- just right, one space after select token, one space before from
assert!(cursor_inbetween_nodes(&mut tree, TextSize::new(7)));
assert!(cursor_inbetween_nodes(&tree, TextSize::new(7)));

// select| from users; <-- still on select token
assert!(!cursor_inbetween_nodes(&mut tree, TextSize::new(6)));
assert!(!cursor_inbetween_nodes(&tree, TextSize::new(6)));

// select |from users; <-- already on from token
assert!(!cursor_inbetween_nodes(&mut tree, TextSize::new(8)));
assert!(!cursor_inbetween_nodes(&tree, TextSize::new(8)));

// select from users;|
assert!(!cursor_inbetween_nodes(&mut tree, TextSize::new(19)));
assert!(!cursor_inbetween_nodes(&tree, TextSize::new(19)));
}

#[test]
@@ -243,29 +243,29 @@ mod tests {
.set_language(tree_sitter_sql::language())
.expect("Error loading sql language");

let mut tree = parser.parse(input, None).unwrap();
let tree = parser.parse(input, None).unwrap();

// select * from| <-- still on previous token
assert!(!cursor_prepared_to_write_token_after_last_node(
&mut tree,
&tree,
TextSize::new(13)
));

// select * from | <-- too far off, two spaces afterward
assert!(!cursor_prepared_to_write_token_after_last_node(
&mut tree,
&tree,
TextSize::new(15)
));

// select * |from <-- it's within
assert!(!cursor_prepared_to_write_token_after_last_node(
&mut tree,
&tree,
TextSize::new(9)
));

// select * from | <-- just right
assert!(cursor_prepared_to_write_token_after_last_node(
&mut tree,
&tree,
TextSize::new(14)
));
}
@@ -295,26 +295,26 @@ mod tests {
.set_language(tree_sitter_sql::language())
.expect("Error loading sql language");

let mut tree = parser.parse(input, None).unwrap();
let tree = parser.parse(input, None).unwrap();

// select * from ;| <-- it's after the statement
assert!(!cursor_before_semicolon(&mut tree, TextSize::new(19)));
assert!(!cursor_before_semicolon(&tree, TextSize::new(19)));

// select * from| ; <-- still touches the from
assert!(!cursor_before_semicolon(&mut tree, TextSize::new(13)));
assert!(!cursor_before_semicolon(&tree, TextSize::new(13)));

// not okay to be ON the semi.
// select * from |;
assert!(!cursor_before_semicolon(&mut tree, TextSize::new(18)));
assert!(!cursor_before_semicolon(&tree, TextSize::new(18)));

// anything is fine here
// select * from | ;
// select * from | ;
// select * from | ;
// select * from |;
assert!(cursor_before_semicolon(&mut tree, TextSize::new(14)));
assert!(cursor_before_semicolon(&mut tree, TextSize::new(15)));
assert!(cursor_before_semicolon(&mut tree, TextSize::new(16)));
assert!(cursor_before_semicolon(&mut tree, TextSize::new(17)));
assert!(cursor_before_semicolon(&tree, TextSize::new(14)));
assert!(cursor_before_semicolon(&tree, TextSize::new(15)));
assert!(cursor_before_semicolon(&tree, TextSize::new(16)));
assert!(cursor_before_semicolon(&tree, TextSize::new(17)));
}
}
8 changes: 5 additions & 3 deletions crates/pgt_completions/src/test_helper.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Display;

use pgt_schema_cache::SchemaCache;
use pgt_test_utils::test_database::get_new_test_db;
use sqlx::Executor;
@@ -25,9 +27,9 @@ impl From<&str> for InputQuery {
}
}

impl ToString for InputQuery {
fn to_string(&self) -> String {
self.sql.clone()
impl Display for InputQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.sql)
}
}

8 changes: 8 additions & 0 deletions crates/pgt_configuration/src/database.rs
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize};
#[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))]
pub struct DatabaseConfiguration {
/// The host of the database.
/// Required if you want database-related features.
/// All else falls back to sensible defaults.
#[partial(bpaf(long("host")))]
pub host: String,

@@ -35,11 +37,17 @@ pub struct DatabaseConfiguration {
/// The connection timeout in seconds.
#[partial(bpaf(long("conn_timeout_secs"), fallback(Some(10)), debug_fallback))]
pub conn_timeout_secs: u16,

/// Actively disable all database-related features.
#[partial(bpaf(long("disable-db"), switch, fallback(Some(false))))]
#[partial(cfg_attr(feature = "schema", schemars(skip)))]
pub disable_connection: bool,
}

impl Default for DatabaseConfiguration {
fn default() -> Self {
Self {
disable_connection: false,
host: "127.0.0.1".to_string(),
port: 5432,
username: "postgres".to_string(),
3 changes: 2 additions & 1 deletion crates/pgt_configuration/src/lib.rs
Original file line number Diff line number Diff line change
@@ -110,8 +110,9 @@ impl PartialConfiguration {
username: Some("postgres".to_string()),
password: Some("postgres".to_string()),
database: Some("postgres".to_string()),
conn_timeout_secs: Some(10),
allow_statement_executions_against: Default::default(),
conn_timeout_secs: Some(10),
disable_connection: Some(false),
}),
}
}
1 change: 0 additions & 1 deletion crates/pgt_lsp/src/session.rs
Original file line number Diff line number Diff line change
@@ -449,7 +449,6 @@ impl Session {
configuration: fs_configuration,
vcs_base_path,
gitignore_matches,
skip_db: false,
});

if let Err(error) = result {
5 changes: 3 additions & 2 deletions crates/pgt_lsp/tests/server.rs
Original file line number Diff line number Diff line change
@@ -773,14 +773,15 @@ async fn test_execute_statement() -> Result<()> {
.to_string();
let host = test_db.connect_options().get_host().to_string();

let conf = PartialConfiguration {
let mut conf = PartialConfiguration::init();
conf.merge_with(PartialConfiguration {
db: Some(PartialDatabaseConfiguration {
database: Some(database),
host: Some(host),
..Default::default()
}),
..Default::default()
};
});

fs.insert(
url!("postgrestools.jsonc").to_file_path().unwrap(),
7 changes: 7 additions & 0 deletions crates/pgt_statement_splitter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -85,6 +85,13 @@ mod tests {
}
}

#[test]
fn ts_with_timezone() {
Tester::from("alter table foo add column bar timestamp with time zone;").expect_statements(
vec!["alter table foo add column bar timestamp with time zone;"],
);
}

#[test]
fn failing_lexer() {
let input = "select 1443ddwwd33djwdkjw13331333333333";
2 changes: 2 additions & 0 deletions crates/pgt_statement_splitter/src/parser/common.rs
Original file line number Diff line number Diff line change
@@ -249,6 +249,8 @@ pub(crate) fn unknown(p: &mut Parser, exclude: &[SyntaxKind]) {
SyntaxKind::Ordinality,
// WITH CHECK should not start a new statement
SyntaxKind::Check,
// TIMESTAMP WITH TIME ZONE should not start a new statement
SyntaxKind::Time,
]
.iter()
.all(|x| Some(x) != next.as_ref())
11 changes: 11 additions & 0 deletions crates/pgt_workspace/src/settings.rs
Original file line number Diff line number Diff line change
@@ -268,6 +268,7 @@ impl Default for LinterSettings {
/// Database settings for the entire workspace
#[derive(Debug)]
pub struct DatabaseSettings {
pub enable_connection: bool,
pub host: String,
pub port: u16,
pub username: String,
@@ -280,6 +281,7 @@ pub struct DatabaseSettings {
impl Default for DatabaseSettings {
fn default() -> Self {
Self {
enable_connection: false,
host: "127.0.0.1".to_string(),
port: 5432,
username: "postgres".to_string(),
@@ -295,6 +297,13 @@ impl From<PartialDatabaseConfiguration> for DatabaseSettings {
fn from(value: PartialDatabaseConfiguration) -> Self {
let d = DatabaseSettings::default();

// "host" is the minimum required setting for database features
// to be enabled.
let enable_connection = value
.host
.as_ref()
.is_some_and(|_| value.disable_connection.is_none_or(|disabled| !disabled));

let database = value.database.unwrap_or(d.database);
let host = value.host.unwrap_or(d.host);

@@ -312,6 +321,8 @@ impl From<PartialDatabaseConfiguration> for DatabaseSettings {
.unwrap_or(false);

Self {
enable_connection,

port: value.port.unwrap_or(d.port),
username: value.username.unwrap_or(d.username),
password: value.password.unwrap_or(d.password),
1 change: 0 additions & 1 deletion crates/pgt_workspace/src/workspace.rs
Original file line number Diff line number Diff line change
@@ -73,7 +73,6 @@ pub struct UpdateSettingsParams {
pub vcs_base_path: Option<PathBuf>,
pub gitignore_matches: Vec<String>,
pub workspace_directory: Option<PathBuf>,
pub skip_db: bool,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
10 changes: 4 additions & 6 deletions crates/pgt_workspace/src/workspace/server.rs
Original file line number Diff line number Diff line change
@@ -168,12 +168,10 @@ impl Workspace for WorkspaceServer {

tracing::info!("Updated settings in workspace");

if !params.skip_db {
self.connection
.write()
.unwrap()
.set_conn_settings(&self.settings().as_ref().db);
}
self.connection
.write()
.unwrap()
.set_conn_settings(&self.settings().as_ref().db);

tracing::info!("Updated Db connection settings");

6 changes: 3 additions & 3 deletions crates/pgt_workspace/src/workspace/server/change.rs
Original file line number Diff line number Diff line change
@@ -1470,7 +1470,7 @@ mod tests {
assert_eq!(old_stmt_text, "select * from");
}

_ => assert!(false, "Did not yield a modified statement."),
_ => unreachable!("Did not yield a modified statement."),
}

assert_document_integrity(&doc);
@@ -1516,7 +1516,7 @@ mod tests {
assert_eq!(old_stmt_text, "select * from");
}

_ => assert!(false, "Did not yield a modified statement."),
_ => unreachable!("Did not yield a modified statement."),
}

assert_document_integrity(&doc);
@@ -1559,7 +1559,7 @@ mod tests {
assert_eq!(new_stmt_text, "select * from users");
}

_ => assert!(false, "Did not yield a modified statement."),
_ => unreachable!("Did not yield a modified statement."),
}

assert_document_integrity(&doc);
5 changes: 5 additions & 0 deletions crates/pgt_workspace/src/workspace/server/db_connection.rs
Original file line number Diff line number Diff line change
@@ -16,6 +16,11 @@ impl DbConnection {
}

pub(crate) fn set_conn_settings(&mut self, settings: &DatabaseSettings) {
if !settings.enable_connection {
tracing::info!("Database connection disabled.");
return;
}

let config = PgConnectOptions::new()
.host(&settings.host)
.port(settings.port)
2 changes: 1 addition & 1 deletion crates/pgt_workspace/src/workspace/server/document.rs
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ impl Document {
.any(|d| d.severity() == Severity::Fatal)
}

pub fn iter<'a>(&'a self) -> StatementIterator<'a> {
pub fn iter(&self) -> StatementIterator<'_> {
StatementIterator::new(self)
}
}
8 changes: 4 additions & 4 deletions crates/pgt_workspace/src/workspace/server/parsed_document.rs
Original file line number Diff line number Diff line change
@@ -353,7 +353,7 @@ impl<'a> StatementMapper<'a> for GetCompletionsMapper {
pub struct GetCompletionsFilter {
pub cursor_position: TextSize,
}
impl<'a> StatementFilter<'a> for GetCompletionsFilter {
impl StatementFilter<'_> for GetCompletionsFilter {
fn predicate(&self, _id: &StatementId, range: &TextRange, content: &str) -> bool {
let is_terminated_by_semi = content.chars().last().is_some_and(|c| c == ';');

@@ -367,7 +367,7 @@ impl<'a> StatementFilter<'a> for GetCompletionsFilter {
}

pub struct NoFilter;
impl<'a> StatementFilter<'a> for NoFilter {
impl StatementFilter<'_> for NoFilter {
fn predicate(&self, _id: &StatementId, _range: &TextRange, _content: &str) -> bool {
true
}
@@ -383,7 +383,7 @@ impl CursorPositionFilter {
}
}

impl<'a> StatementFilter<'a> for CursorPositionFilter {
impl StatementFilter<'_> for CursorPositionFilter {
fn predicate(&self, _id: &StatementId, range: &TextRange, _content: &str) -> bool {
range.contains(self.pos)
}
@@ -399,7 +399,7 @@ impl IdFilter {
}
}

impl<'a> StatementFilter<'a> for IdFilter {
impl StatementFilter<'_> for IdFilter {
fn predicate(&self, id: &StatementId, _range: &TextRange, _content: &str) -> bool {
*id == self.id
}
2 changes: 1 addition & 1 deletion docs/schemas/0.0.0/schema.json
Original file line number Diff line number Diff line change
@@ -100,7 +100,7 @@
]
},
"host": {
"description": "The host of the database.",
"description": "The host of the database. Required if you want database-related features. All else falls back to sensible defaults.",
"type": [
"string",
"null"
2 changes: 1 addition & 1 deletion docs/schemas/latest/schema.json
Original file line number Diff line number Diff line change
@@ -100,7 +100,7 @@
]
},
"host": {
"description": "The host of the database.",
"description": "The host of the database. Required if you want database-related features. All else falls back to sensible defaults.",
"type": [
"string",
"null"
33 changes: 29 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
@@ -10,16 +10,15 @@ alias rg := reset-git
# Installs the tools needed to develop
install-tools:
cargo install cargo-binstall
cargo binstall cargo-insta taplo-cli
cargo binstall cargo-insta taplo-cli sqlx-cli
cargo binstall --git "https://github.com/astral-sh/uv" uv
bun install

# Upgrades the tools needed to develop
upgrade-tools:
cargo install cargo-binstall --force
cargo binstall cargo-insta taplo-cli --force
cargo binstall cargo-insta taplo-cli sqlx-cli --force
cargo binstall --git "https://github.com/astral-sh/uv" uv --force
bun install

# Generates code generated files for the linter
gen-lint:
@@ -41,6 +40,16 @@ format:
taplo format
bun biome format --write

format-ci:
cargo fmt --all --check
taplo format --check
bun biome format

format-ci-versions:
cargo --version
taplo --version
echo "Biome $(bun biome --version)"

[unix]
_touch file:
touch {{file}}
@@ -72,11 +81,27 @@ lint-fix:
cargo run -p rules_check
bun biome lint --write

lint-ci-versions:
rustc --version
rustup --version
cargo --version
cargo sqlx --version
cargo clippy --version
echo "Biome $(bun biome --version)"

lint-ci:
cargo sqlx prepare --check --workspace
cargo clippy --fix
cargo run -p rules_check
bun biome lint --write

serve-docs:
uv sync
uv run mkdocs serve

# When you finished coding, run this command. Note that you should have already committed your changes.
# If you haven't run `sqlx prepare` at least once, you need to run `docker compose up`
# to lint the queries.
ready:
git diff --exit-code --quiet
cargo run -p xtask_codegen -- configuration
@@ -113,4 +138,4 @@ merge-main:
# We recommend to install `bunyan` (npm i -g bunyan) and pipe the output through there for color-coding:
# just show-logs | bunyan
show-logs:
tail -f $(ls $PGT_LOG_PATH/server.log.* | sort -t- -k2,2 -k3,3 -k4,4 | tail -n 1)
tail -f $(ls $PGT_LOG_PATH/server.log.* | sort -t- -k2,2 -k3,3 -k4,4 | tail -n 1)
27 changes: 21 additions & 6 deletions packages/@postgrestools/backend-jsonrpc/src/workspace.ts
Original file line number Diff line number Diff line change
@@ -179,21 +179,36 @@ export interface GetCompletionsParams {
*/
position: TextSize;
}
export interface CompletionResult {
export interface CompletionsResult {
items: CompletionItem[];
}
export interface CompletionItem {
completion_text?: CompletionText;
description: string;
kind: CompletionItemKind;
label: string;
preselected: boolean;
score: number;
/**
* String used for sorting by LSP clients.
*/
sort_text: string;
}
/**
* The text that the editor should fill in. If `None`, the `label` should be used. Tables, for example, might have different completion_texts:
label: "users", description: "Schema: auth", completion_text: "auth.users".
*/
export interface CompletionText {
/**
* A `range` is required because some editors replace the current token, others naively insert the text. Having a range where start == end makes it an insertion.
*/
range: TextRange;
text: string;
}
export type CompletionItemKind = "table" | "function" | "column";
export type CompletionItemKind = "table" | "function" | "column" | "schema";
export interface UpdateSettingsParams {
configuration: PartialConfiguration;
gitignore_matches: string[];
skip_db: boolean;
vcs_base_path?: string;
workspace_directory?: string;
}
@@ -240,7 +255,7 @@ export interface PartialDatabaseConfiguration {
*/
database?: string;
/**
* The host of the database.
* The host of the database. Required if you want database-related features. All else falls back to sensible defaults.
*/
host?: string;
/**
@@ -414,7 +429,7 @@ export interface Workspace {
pullDiagnostics(
params: PullDiagnosticsParams,
): Promise<PullDiagnosticsResult>;
getCompletions(params: GetCompletionsParams): Promise<CompletionResult>;
getCompletions(params: GetCompletionsParams): Promise<CompletionsResult>;
updateSettings(params: UpdateSettingsParams): Promise<void>;
openFile(params: OpenFileParams): Promise<void>;
changeFile(params: ChangeFileParams): Promise<void>;
6 changes: 1 addition & 5 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[toolchain]
# The default profile includes rustc, rust-std, cargo, rust-docs, rustfmt and clippy.
# https://rust-lang.github.io/rustup/concepts/profiles.html
profile = "default"
# For some reason, rustfmt is not included in the default profile. Add it here.
components = ["rustfmt"]
channel = "nightly"
channel = "1.86.0"
Loading