use la_arena::Arena;
use rowan::TextSize;
use squawk_syntax::{SyntaxNodePtr, ast, ast::AstNode};
use crate::scope::Scope;
use crate::symbols::{Name, Schema, Symbol, SymbolKind};
#[derive(Clone, PartialEq)]
struct SearchPathChange {
position: TextSize,
search_path: Vec<Schema>,
}
#[derive(Clone, PartialEq)]
pub(crate) struct Binder {
scope: Scope,
symbols: Arena<Symbol>,
search_path_changes: Vec<SearchPathChange>,
}
impl Binder {
fn new() -> Self {
Binder {
scope: Scope::default(),
symbols: Arena::new(),
search_path_changes: vec![SearchPathChange {
position: TextSize::from(0),
search_path: vec![
Schema::new("public"),
Schema::new("pg_temp"),
Schema::new("pg_catalog"),
],
}],
}
}
pub(crate) fn lookup(&self, name: &Name, kind: SymbolKind) -> Option<SyntaxNodePtr> {
let symbols = self.scope.get(name)?;
let symbol_id = symbols.iter().copied().find(|id| {
let symbol = &self.symbols[*id];
symbol.kind == kind
})?;
Some(self.symbols[symbol_id].ptr)
}
pub(crate) fn lookup_with(
&self,
name: &Name,
kind: SymbolKind,
position: TextSize,
schema: &Option<Schema>,
) -> Option<SyntaxNodePtr> {
let symbols = self.scope.get(name)?;
let search_paths = match schema {
Some(s) => std::slice::from_ref(s),
None => self.search_path_at(position),
};
for search_schema in search_paths {
if let Some(symbol_id) = symbols.iter().copied().find(|id| {
let symbol = &self.symbols[*id];
symbol.kind == kind && symbol.schema.as_ref() == Some(search_schema)
}) {
return Some(self.symbols[symbol_id].ptr);
}
}
None
}
pub(crate) fn lookup_with_params(
&self,
name: &Name,
kind: SymbolKind,
position: TextSize,
schema: &Option<Schema>,
params: Option<&[Name]>,
) -> Option<SyntaxNodePtr> {
let symbols = self.scope.get(name)?;
let search_paths = match schema {
Some(s) => std::slice::from_ref(s),
None => self.search_path_at(position),
};
for search_schema in search_paths {
if let Some(symbol_id) = symbols.iter().copied().find(|id| {
let symbol = &self.symbols[*id];
let params_match = match (&symbol.params, params) {
(Some(sym_params), Some(req_params)) => sym_params.as_slice() == req_params,
(None, None) => true,
(_, None) => true,
_ => false,
};
symbol.kind == kind && symbol.schema.as_ref() == Some(search_schema) && params_match
}) {
return Some(self.symbols[symbol_id].ptr);
}
}
None
}
pub(crate) fn lookup_with_table(
&self,
name: &Name,
kind: SymbolKind,
position: TextSize,
schema: &Option<Schema>,
table: &Option<Name>,
) -> Option<SyntaxNodePtr> {
let symbols = self.scope.get(name)?;
let search_paths = match schema {
Some(s) => std::slice::from_ref(s),
None => self.search_path_at(position),
};
for search_schema in search_paths {
if let Some(symbol_id) = symbols.iter().copied().find(|id| {
let symbol = &self.symbols[*id];
symbol.kind == kind
&& symbol.schema.as_ref() == Some(search_schema)
&& &symbol.table == table
}) {
return Some(self.symbols[symbol_id].ptr);
}
}
None
}
pub(crate) fn lookup_info(
&self,
name: &Name,
schema: Option<&Schema>,
kind: SymbolKind,
position: TextSize,
) -> Option<(Schema, String)> {
let symbols = self.scope.get(name)?;
let search_paths: &[Schema] = match schema {
Some(s) => std::slice::from_ref(s),
None => self.search_path_at(position),
};
for search_schema in search_paths {
if let Some(symbol_id) = symbols.iter().copied().find(|id| {
let symbol = &self.symbols[*id];
symbol.kind == kind && symbol.schema.as_ref() == Some(search_schema)
}) {
let symbol = &self.symbols[symbol_id];
return Some((symbol.schema.clone()?, name.to_string()));
}
}
None
}
fn current_search_path(&self) -> &[Schema] {
&self
.search_path_changes
.last()
.expect("search_path_changes should never be empty")
.search_path
}
pub(crate) fn search_path_at(&self, position: TextSize) -> &[Schema] {
for change in self.search_path_changes.iter().rev() {
if change.position <= position {
return &change.search_path;
}
}
&self.search_path_changes[0].search_path
}
pub(crate) fn all_symbols_by_kind(
&self,
kind: SymbolKind,
schema: Option<&Schema>,
) -> Vec<&Name> {
let mut names = vec![];
for (name, symbol_ids) in &self.scope.entries {
for symbol_id in symbol_ids {
let symbol = &self.symbols[*symbol_id];
if symbol.kind == kind
&& schema.is_none_or(|schema| symbol.schema.as_ref() == Some(schema))
{
names.push(name);
break;
}
}
}
names
}
pub(crate) fn functions_with_single_param(&self, param_type: &Name) -> Vec<&Name> {
let mut names = vec![];
for (name, symbol_ids) in &self.scope.entries {
for symbol_id in symbol_ids {
let symbol = &self.symbols[*symbol_id];
if symbol.kind == SymbolKind::Function
&& symbol
.params
.as_ref()
.is_some_and(|p| p.len() == 1 && &p[0] == param_type)
{
names.push(name);
break;
}
}
}
names
}
}
pub(crate) fn bind(file: &ast::SourceFile) -> Binder {
let mut binder = Binder::new();
bind_file(&mut binder, file);
binder
}
fn bind_file(b: &mut Binder, file: &ast::SourceFile) {
for stmt in file.stmts() {
bind_stmt(b, stmt);
}
}
fn bind_stmt(b: &mut Binder, stmt: ast::Stmt) {
match stmt {
ast::Stmt::CreateTable(create_table) => bind_create_table(b, create_table),
ast::Stmt::CreateTableAs(create_table_as) => bind_create_table_as(b, create_table_as),
ast::Stmt::SelectInto(select_into) => bind_select_into(b, select_into),
ast::Stmt::CreateForeignTable(create_foreign_table) => {
bind_create_table(b, create_foreign_table)
}
ast::Stmt::CreateIndex(create_index) => bind_create_index(b, create_index),
ast::Stmt::CreateFunction(create_function) => bind_create_function(b, create_function),
ast::Stmt::CreateAggregate(create_aggregate) => bind_create_aggregate(b, create_aggregate),
ast::Stmt::CreateProcedure(create_procedure) => bind_create_procedure(b, create_procedure),
ast::Stmt::CreateSchema(create_schema) => bind_create_schema(b, create_schema),
ast::Stmt::CreateType(create_type) => bind_create_type(b, create_type),
ast::Stmt::CreateDomain(create_domain) => bind_create_domain(b, create_domain),
ast::Stmt::CreateView(create_view) => bind_create_view(b, create_view),
ast::Stmt::CreateMaterializedView(create_view) => {
bind_create_materialized_view(b, create_view)
}
ast::Stmt::CreateSequence(create_sequence) => bind_create_sequence(b, create_sequence),
ast::Stmt::CreateTrigger(create_trigger) => bind_create_trigger(b, create_trigger),
ast::Stmt::CreateEventTrigger(create_event_trigger) => {
bind_create_event_trigger(b, create_event_trigger)
}
ast::Stmt::CreateTablespace(create_tablespace) => {
bind_create_tablespace(b, create_tablespace)
}
ast::Stmt::CreateDatabase(create_database) => bind_create_database(b, create_database),
ast::Stmt::CreateServer(create_server) => bind_create_server(b, create_server),
ast::Stmt::CreateExtension(create_extension) => bind_create_extension(b, create_extension),
ast::Stmt::CreateRole(create_role) => bind_create_role(b, create_role),
ast::Stmt::Declare(declare) => bind_declare_cursor(b, declare),
ast::Stmt::Prepare(prepare) => bind_prepare(b, prepare),
ast::Stmt::Listen(listen) => bind_listen(b, listen),
ast::Stmt::Set(set) => bind_set(b, set),
ast::Stmt::CreatePolicy(create_policy) => bind_create_policy(b, create_policy),
ast::Stmt::CreatePropertyGraph(create_property_graph) => {
bind_create_property_graph(b, create_property_graph)
}
_ => (),
}
}
fn bind_create_table(b: &mut Binder, create_table: impl ast::HasCreateTable) {
let Some(path) = create_table.path() else {
return;
};
let Some(table_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let is_temp = create_table
.persistence()
.is_some_and(|p| matches!(p, ast::Persistence::Temp(_)));
let Some(schema) = schema_name(b, &path, is_temp) else {
return;
};
let table_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Table,
ptr: name_ptr,
schema: Some(schema.clone()),
params: None,
table: None,
});
let type_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Type,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(table_name.clone(), table_id);
b.scope.insert(table_name, type_id);
}
fn bind_create_table_as(b: &mut Binder, create_table_as: ast::CreateTableAs) {
let Some(path) = create_table_as.path() else {
return;
};
let Some(table_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let is_temp = create_table_as
.persistence()
.is_some_and(|p| matches!(p, ast::Persistence::Temp(_)));
let Some(schema) = schema_name(b, &path, is_temp) else {
return;
};
let table_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Table,
ptr: name_ptr,
schema: Some(schema.clone()),
params: None,
table: None,
});
let type_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Type,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(table_name.clone(), table_id);
b.scope.insert(table_name, type_id);
}
fn bind_select_into(b: &mut Binder, select_into: ast::SelectInto) {
let Some(into_clause) = select_into.into_clause() else {
return;
};
let Some(path) = into_clause.path() else {
return;
};
let Some(table_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let is_temp = into_clause
.persistence()
.is_some_and(|p| matches!(p, ast::Persistence::Temp(_)));
let Some(schema) = schema_name(b, &path, is_temp) else {
return;
};
let table_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Table,
ptr: name_ptr,
schema: Some(schema.clone()),
params: None,
table: None,
});
let type_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Type,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(table_name.clone(), table_id);
b.scope.insert(table_name, type_id);
}
fn bind_create_index(b: &mut Binder, create_index: ast::CreateIndex) {
let Some(name) = create_index.name() else {
return;
};
let index_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let Some(schema) = b.current_search_path().first().cloned() else {
return;
};
let index_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Index,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(index_name, index_id);
}
fn bind_create_function(b: &mut Binder, create_function: ast::CreateFunction) {
let Some(path) = create_function.path() else {
return;
};
let Some(function_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let Some(schema) = schema_name(b, &path, false) else {
return;
};
let params = extract_param_signature(create_function.param_list());
let function_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Function,
ptr: name_ptr,
schema: Some(schema),
params,
table: None,
});
b.scope.insert(function_name, function_id);
}
fn bind_create_aggregate(b: &mut Binder, create_aggregate: ast::CreateAggregate) {
let Some(path) = create_aggregate.path() else {
return;
};
let Some(aggregate_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let Some(schema) = schema_name(b, &path, false) else {
return;
};
let params = extract_param_signature(create_aggregate.param_list());
let aggregate_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Aggregate,
ptr: name_ptr,
schema: Some(schema),
params,
table: None,
});
b.scope.insert(aggregate_name, aggregate_id);
}
fn bind_create_procedure(b: &mut Binder, create_procedure: ast::CreateProcedure) {
let Some(path) = create_procedure.path() else {
return;
};
let Some(procedure_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let Some(schema) = schema_name(b, &path, false) else {
return;
};
let params = extract_param_signature(create_procedure.param_list());
let procedure_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Procedure,
ptr: name_ptr,
schema: Some(schema),
params,
table: None,
});
b.scope.insert(procedure_name, procedure_id);
}
fn bind_create_schema(b: &mut Binder, create_schema: ast::CreateSchema) {
let (schema_name, name_ptr) = if let Some(schema_name_node) = create_schema.name() {
let schema_name = Name::from_node(&schema_name_node);
let name_ptr = SyntaxNodePtr::new(schema_name_node.syntax());
(schema_name, name_ptr)
} else if let Some(name) = create_schema.role().and_then(|role| role.name()) {
let schema_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
(schema_name, name_ptr)
} else {
return;
};
let schema_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Schema,
ptr: name_ptr,
schema: Some(Schema(schema_name.clone())),
params: None,
table: None,
});
b.scope.insert(schema_name, schema_id);
}
fn bind_create_type(b: &mut Binder, create_type: ast::CreateType) {
let Some(path) = create_type.path() else {
return;
};
let Some(type_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let Some(schema) = schema_name(b, &path, false) else {
return;
};
let type_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Type,
ptr: name_ptr,
schema: Some(schema.clone()),
params: None,
table: None,
});
b.scope.insert(type_name.clone(), type_id);
if create_type.range_token().is_some() {
if let Some((multirange_name, multirange_ptr, multirange_schema)) =
multirange_type_from_range(b, create_type, type_name, schema, name_ptr)
{
let multirange_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Type,
ptr: multirange_ptr,
schema: Some(multirange_schema),
params: None,
table: None,
});
b.scope.insert(multirange_name, multirange_id);
}
}
}
fn bind_create_domain(b: &mut Binder, create_domain: ast::CreateDomain) {
let Some(path) = create_domain.path() else {
return;
};
let Some(domain_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let Some(schema) = schema_name(b, &path, false) else {
return;
};
let type_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Type,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(domain_name, type_id);
}
fn multirange_type_from_range(
b: &Binder,
create_type: ast::CreateType,
type_name: Name,
schema: Schema,
fallback_ptr: SyntaxNodePtr,
) -> Option<(Name, SyntaxNodePtr, Schema)> {
if let Some(attribute_list) = create_type.attribute_list() {
let multirange_key = Name::from_string("multirange_type_name");
for option in attribute_list.attribute_options() {
let Some(name) = option.name() else {
continue;
};
if Name::from_node(&name) != multirange_key {
continue;
}
if let Some(attribute_value) = option.attribute_value() {
if let Some(literal) = attribute_value.literal()
&& let Some(string_value) = extract_string_literal(&literal)
{
let multirange_name = Name::from_string(string_value);
return Some((multirange_name, fallback_ptr, schema));
}
if let Some(ast::Type::PathType(path_type)) = attribute_value.ty()
&& let Some(path) = path_type.path()
&& let Some(multirange_name) = item_name(&path)
{
let multirange_schema = if path.qualifier().is_some() {
schema_name(b, &path, false)?
} else {
schema
};
return Some((multirange_name, fallback_ptr, multirange_schema));
}
}
}
}
let multirange_name = derive_multirange_name(type_name);
Some((multirange_name, fallback_ptr, schema))
}
fn derive_multirange_name(range_name: Name) -> Name {
let range_text = range_name.0.as_str();
if range_text.contains("range") {
Name::from_string(range_text.replacen("range", "multirange", 1))
} else {
Name::from_string(format!("{range_text}_multirange"))
}
}
fn bind_create_view(b: &mut Binder, create_view: ast::CreateView) {
let Some(path) = create_view.path() else {
return;
};
let Some(view_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let is_temp = create_view
.persistence()
.is_some_and(|p| matches!(p, ast::Persistence::Temp(_)));
let Some(schema) = schema_name(b, &path, is_temp) else {
return;
};
let view_id = b.symbols.alloc(Symbol {
kind: SymbolKind::View,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(view_name, view_id);
}
fn bind_create_materialized_view(b: &mut Binder, create_view: ast::CreateMaterializedView) {
let Some(path) = create_view.path() else {
return;
};
let Some(view_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let Some(schema) = schema_name(b, &path, false) else {
return;
};
let view_id = b.symbols.alloc(Symbol {
kind: SymbolKind::View,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(view_name, view_id);
}
fn bind_create_sequence(b: &mut Binder, create_sequence: ast::CreateSequence) {
let Some(path) = create_sequence.path() else {
return;
};
let Some(sequence_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let is_temp = create_sequence
.persistence()
.is_some_and(|p| matches!(p, ast::Persistence::Temp(_)));
let Some(schema) = schema_name(b, &path, is_temp) else {
return;
};
let sequence_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Sequence,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(sequence_name, sequence_id);
}
fn bind_create_trigger(b: &mut Binder, create_trigger: ast::CreateTrigger) {
let Some(name) = create_trigger.name() else {
return;
};
let trigger_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let Some(table_path) = create_trigger.on_table().and_then(|on| on.path()) else {
return;
};
let Some(table_name) = item_name(&table_path) else {
return;
};
let Some(schema) = schema_name(b, &table_path, false) else {
return;
};
let trigger_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Trigger,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: Some(table_name),
});
b.scope.insert(trigger_name, trigger_id);
}
fn bind_create_policy(b: &mut Binder, create_policy: ast::CreatePolicy) {
let Some(name) = create_policy.name() else {
return;
};
let policy_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let Some(table_path) = create_policy.on_table().and_then(|on| on.path()) else {
return;
};
let Some(table_name) = item_name(&table_path) else {
return;
};
let Some(schema) = schema_name(b, &table_path, false) else {
return;
};
let policy_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Policy,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: Some(table_name),
});
b.scope.insert(policy_name, policy_id);
}
fn bind_create_property_graph(b: &mut Binder, create_property_graph: ast::CreatePropertyGraph) {
let Some(path) = create_property_graph.path() else {
return;
};
let Some(property_graph_name) = item_name(&path) else {
return;
};
let name_ptr = path_to_ptr(&path);
let Some(schema) = schema_name(b, &path, false) else {
return;
};
let property_graph_id = b.symbols.alloc(Symbol {
kind: SymbolKind::PropertyGraph,
ptr: name_ptr,
schema: Some(schema),
params: None,
table: None,
});
b.scope.insert(property_graph_name, property_graph_id);
}
fn bind_create_event_trigger(b: &mut Binder, create_event_trigger: ast::CreateEventTrigger) {
let Some(name) = create_event_trigger.name() else {
return;
};
let event_trigger_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let event_trigger_id = b.symbols.alloc(Symbol {
kind: SymbolKind::EventTrigger,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(event_trigger_name, event_trigger_id);
}
fn bind_create_tablespace(b: &mut Binder, create_tablespace: ast::CreateTablespace) {
let Some(name) = create_tablespace.name() else {
return;
};
let tablespace_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let tablespace_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Tablespace,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(tablespace_name, tablespace_id);
}
fn bind_create_database(b: &mut Binder, create_database: ast::CreateDatabase) {
let Some(name) = create_database.name() else {
return;
};
let database_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let database_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Database,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(database_name, database_id);
}
fn bind_create_server(b: &mut Binder, create_server: ast::CreateServer) {
let Some(name) = create_server.name() else {
return;
};
let server_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let server_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Server,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(server_name, server_id);
}
fn bind_create_extension(b: &mut Binder, create_extension: ast::CreateExtension) {
let Some(name) = create_extension.name() else {
return;
};
let extension_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let extension_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Extension,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(extension_name, extension_id);
}
fn bind_create_role(b: &mut Binder, create_role: ast::CreateRole) {
let Some(name) = create_role.name() else {
return;
};
let role_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let role_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Role,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(role_name, role_id);
}
fn bind_declare_cursor(b: &mut Binder, declare: ast::Declare) {
let Some(name) = declare.name() else {
return;
};
let cursor_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let cursor_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Cursor,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(cursor_name, cursor_id);
}
fn bind_prepare(b: &mut Binder, prepare: ast::Prepare) {
let Some(name) = prepare.name() else {
return;
};
let statement_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let statement_id = b.symbols.alloc(Symbol {
kind: SymbolKind::PreparedStatement,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(statement_name, statement_id);
}
fn bind_listen(b: &mut Binder, listen: ast::Listen) {
let Some(name) = listen.name() else {
return;
};
let channel_name = Name::from_node(&name);
let name_ptr = SyntaxNodePtr::new(name.syntax());
let channel_id = b.symbols.alloc(Symbol {
kind: SymbolKind::Channel,
ptr: name_ptr,
schema: None,
params: None,
table: None,
});
b.scope.insert(channel_name, channel_id);
}
fn item_name(path: &ast::Path) -> Option<Name> {
let segment = path.segment()?;
if let Some(name) = segment.name() {
return Some(Name::from_node(&name));
}
if let Some(name) = segment.name_ref() {
return Some(Name::from_node(&name));
}
None
}
fn path_to_ptr(path: &ast::Path) -> SyntaxNodePtr {
if let Some(segment) = path.segment() {
if let Some(name) = segment.name() {
return SyntaxNodePtr::new(name.syntax());
}
if let Some(name_ref) = segment.name_ref() {
return SyntaxNodePtr::new(name_ref.syntax());
}
}
SyntaxNodePtr::new(path.syntax())
}
fn schema_name(b: &Binder, path: &ast::Path, is_temp: bool) -> Option<Schema> {
if let Some(name_ref) = path
.qualifier()
.and_then(|q| q.segment())
.and_then(|s| s.name_ref())
{
return Some(Schema(Name::from_node(&name_ref)));
}
if is_temp {
return Some(Schema::new("pg_temp"));
}
b.current_search_path().first().cloned()
}
fn bind_set(b: &mut Binder, set: ast::Set) {
let position = set.syntax().text_range().start();
if set.schema_token().is_some() {
if let Some(literal) = set.literal()
&& let Some(string_value) = extract_string_literal(&literal)
{
b.search_path_changes.push(SearchPathChange {
position,
search_path: vec![Schema::new(string_value)],
});
}
return;
}
let Some(path) = set.path() else { return };
if path.qualifier().is_some() {
return;
}
let Some(segment) = path.segment() else {
return;
};
let param_name = if let Some(name_ref) = segment.name_ref() {
name_ref.syntax().text().to_string()
} else {
return;
};
if !param_name.eq_ignore_ascii_case("search_path") {
return;
}
if set.default_token().is_some() {
b.search_path_changes.push(SearchPathChange {
position,
search_path: vec![
Schema::new("public"),
Schema::new("pg_temp"),
Schema::new("pg_catalog"),
],
});
} else {
let mut search_path = vec![];
for config_value in set.config_values() {
match config_value {
ast::ConfigValue::Literal(literal) => {
if let Some(string_value) = extract_string_literal(&literal) {
if !string_value.is_empty() {
search_path.push(Schema::new(string_value));
}
}
}
ast::ConfigValue::NameRef(name_ref) => {
let schema_name = name_ref.syntax().text().to_string();
search_path.push(Schema::new(schema_name));
}
}
}
b.search_path_changes.push(SearchPathChange {
position,
search_path,
});
}
}
pub(crate) fn extract_string_literal(literal: &ast::Literal) -> Option<String> {
let text = literal.syntax().text().to_string();
if text.starts_with('\'') && text.ends_with('\'') && text.len() >= 2 {
Some(text[1..text.len() - 1].to_string())
} else {
None
}
}
fn extract_param_signature(param_list: Option<ast::ParamList>) -> Option<Vec<Name>> {
let param_list = param_list?;
let mut params = vec![];
for param in param_list.params() {
if let Some(ty) = param.ty()
&& let ast::Type::PathType(path_type) = ty
&& let Some(path) = path_type.path()
&& let Some(segment) = path.segment()
&& let Some(name_ref) = segment.name_ref()
{
params.push(Name::from_node(&name_ref));
}
}
(!params.is_empty()).then_some(params)
}