use std::sync::Arc;
use async_trait::async_trait;
use pgwire::api::results::{FieldFormat, FieldInfo};
use pgwire::api::stmt::QueryParser;
use pgwire::api::{ClientInfo, Type};
use pgwire::error::{ErrorInfo, PgWireError, PgWireResult};
use crate::config::auth::AuthMode;
use crate::control::security::audit::ArcAuditEmitter;
use crate::control::server::response_shape::types::DdlColType;
use crate::control::server::shared::authorization::{authorize_database, authorize_task_set};
use crate::control::server::shared::session::SessionStore;
use crate::control::state::SharedState;
use super::super::auth::{pgwire_authorization_error, resolve_session_identity};
use super::statement::ParsedStatement;
use parser_schema::{
count_placeholders, is_dsl_statement, result_fields_for_returning,
substitute_placeholders_with_null,
};
#[path = "parser_schema.rs"]
mod parser_schema;
fn ddl_col_type_to_pg(ty: &DdlColType) -> Type {
match ty {
DdlColType::Int8 => Type::INT8,
DdlColType::Int4 => Type::INT4,
DdlColType::Int2 => Type::INT2,
DdlColType::Float8 => Type::FLOAT8,
DdlColType::Float4 => Type::FLOAT4,
DdlColType::Text => Type::TEXT,
DdlColType::Bool => Type::BOOL,
DdlColType::Bytea => Type::BYTEA,
DdlColType::Json => Type::JSON,
DdlColType::Jsonb => Type::JSONB,
DdlColType::Timestamp => Type::TIMESTAMP,
DdlColType::Timestamptz => Type::TIMESTAMPTZ,
DdlColType::Varchar => Type::VARCHAR,
DdlColType::Float4Array => Type::FLOAT4_ARRAY,
DdlColType::Float8Array => Type::FLOAT8_ARRAY,
}
}
pub struct NodeDbQueryParser {
state: Arc<SharedState>,
auth_mode: AuthMode,
sessions: Arc<SessionStore>,
}
impl NodeDbQueryParser {
pub fn new(state: Arc<SharedState>, auth_mode: AuthMode, sessions: Arc<SessionStore>) -> Self {
Self {
state,
auth_mode,
sessions,
}
}
fn placeholder_types(sql: &str, client_types: &[Option<Type>]) -> Vec<Option<Type>> {
let param_count = count_placeholders(sql);
let mut param_types = vec![None; param_count.max(client_types.len())];
for (index, client_type) in client_types.iter().enumerate() {
if let Some(client_type) = client_type {
param_types[index] = Some(client_type.clone());
}
}
param_types
}
async fn authorize_plannable_sql(
&self,
sql: &str,
identity: &crate::control::security::identity::AuthenticatedIdentity,
database_id: crate::types::DatabaseId,
emitter: &ArcAuditEmitter,
) -> PgWireResult<bool> {
let (sql_without_returning, _) =
match crate::control::server::pgwire::handler::returning::strip_returning(sql) {
Ok(parts) => parts,
Err(_) => return Ok(false),
};
let sql_for_planning = substitute_placeholders_with_null(&sql_without_returning);
let query_ctx =
crate::control::planner::context::QueryContext::for_state_with_lease(&self.state);
let auth_ctx = crate::control::server::session_auth::build_auth_context(identity);
let permission_cache = self.state.permission_cache.read().await;
let security = crate::control::planner::context::PlanSecurityContext {
identity,
auth: &auth_ctx,
rls_store: &self.state.rls,
permissions: &self.state.permissions,
roles: &self.state.roles,
permission_cache: Some(&*permission_cache),
};
let Ok((mut tasks, _)) = query_ctx
.plan_sql_with_rls(crate::control::planner::context::PlanSqlWithRlsParams {
sql: &sql_for_planning,
tenant_id: identity.tenant_id,
database_id,
sec: &security,
})
.await
else {
return Ok(false);
};
drop(permission_cache);
crate::control::planner::implicit_edges::append_implicit_edge_tasks(
&self.state,
&mut tasks,
identity.tenant_id,
database_id,
crate::types::TraceId::ZERO,
)
.await
.map_err(|error| {
PgWireError::UserError(Box::new(ErrorInfo::new(
"ERROR".to_owned(),
"XX000".to_owned(),
error.to_string(),
)))
})?;
authorize_task_set(
identity,
&tasks,
&self.state.permissions,
&self.state.roles,
emitter,
)
.map_err(pgwire_authorization_error)?;
Ok(true)
}
fn try_infer_types(
&self,
sql: &str,
client_types: &[Option<Type>],
tenant_id: u64,
database_id: crate::types::DatabaseId,
) -> (Vec<Option<Type>>, Vec<FieldInfo>) {
let catalog = crate::control::planner::catalog_adapter::OriginCatalog::new(
Arc::clone(&self.state.credentials),
tenant_id,
database_id,
Some(Arc::clone(&self.state.retention_policy_registry)),
);
let param_types = Self::placeholder_types(sql, client_types);
let (sql_stripped, returning_spec) =
match crate::control::server::pgwire::handler::returning::strip_returning(sql) {
Ok(pair) => pair,
Err(_) => return (param_types, Vec::new()),
};
let sql_for_inference = substitute_placeholders_with_null(&sql_stripped);
let plans = match nodedb_sql::plan_sql(&sql_for_inference, &catalog) {
Ok(p) => p,
Err(_) => return (param_types, Vec::new()),
};
if let Some(spec) = returning_spec
&& let Some(fields) = result_fields_for_returning(&spec, plans.first(), &catalog)
{
return (param_types, fields);
}
let output_schema =
crate::control::planner::sql_plan_convert::output_schema::build_output_schema(
&plans,
&catalog,
database_id,
);
let result_fields: Vec<FieldInfo> = output_schema
.columns
.iter()
.map(|c| {
FieldInfo::new(
c.display_name.clone(),
None,
None,
ddl_col_type_to_pg(&c.ty),
FieldFormat::Text,
)
})
.collect();
(param_types, result_fields)
}
}
#[async_trait]
impl QueryParser for NodeDbQueryParser {
type Statement = ParsedStatement;
async fn parse_sql<C>(
&self,
client: &C,
sql: &str,
types: &[Option<Type>],
) -> PgWireResult<Self::Statement>
where
C: ClientInfo + Unpin + Send + Sync,
{
let addr = client.socket_addr();
let identity = resolve_session_identity(
&self.state,
self.auth_mode.clone(),
&self.sessions,
client,
&addr,
)?;
let database_id = self
.sessions
.get_current_database(&addr)
.unwrap_or(crate::types::DatabaseId::DEFAULT);
let emitter = ArcAuditEmitter(Arc::clone(&self.state.audit));
authorize_database(&identity, database_id, &emitter).map_err(pgwire_authorization_error)?;
if crate::control::backup::detect(sql).is_some() {
return Ok(ParsedStatement {
sql: sql.to_owned(),
param_types: Vec::new(),
result_fields: Vec::new(),
is_dsl: false,
});
}
let can_infer_schema = self
.authorize_plannable_sql(sql, &identity, database_id, &emitter)
.await?;
let (param_types, result_fields) = if can_infer_schema {
self.try_infer_types(sql, types, identity.tenant_id.as_u64(), database_id)
} else {
(Self::placeholder_types(sql, types), Vec::new())
};
let is_dsl = result_fields.is_empty() && is_dsl_statement(sql);
Ok(ParsedStatement {
sql: sql.to_owned(),
param_types,
result_fields,
is_dsl,
})
}
fn get_parameter_types(&self, stmt: &Self::Statement) -> PgWireResult<Vec<Type>> {
Ok(stmt
.param_types
.iter()
.map(|t| t.clone().unwrap_or(Type::UNKNOWN))
.collect())
}
fn get_result_schema(
&self,
stmt: &Self::Statement,
_column_format: Option<&pgwire::api::portal::Format>,
) -> PgWireResult<Vec<FieldInfo>> {
Ok(stmt.result_fields.clone())
}
}