1use super::http_utils::get_client_ip;
2use super::query_audit::{QueryAuditRecord, ScanMetrics, aggregate_scan_metrics};
3use super::sqlinfo::{
4 SQL_INFO_DATE_TIME_FUNCTIONS, SQL_INFO_NUMERIC_FUNCTIONS, SQL_INFO_SQL_KEYWORDS,
5 SQL_INFO_STRING_FUNCTIONS, SQL_INFO_SYSTEM_FUNCTIONS,
6};
7use anyhow::Result;
8use arrow_flight::decode::FlightRecordBatchStream;
9use arrow_flight::encode::{DictionaryHandling, FlightDataEncoderBuilder};
10use arrow_flight::error::FlightError;
11use arrow_flight::sql::DoPutPreparedStatementResult;
12use arrow_flight::sql::metadata::{SqlInfoData, SqlInfoDataBuilder};
13use arrow_flight::sql::server::PeekableFlightDataStream;
14use arrow_flight::sql::{
15 ActionBeginSavepointRequest, ActionBeginSavepointResult, ActionBeginTransactionRequest,
16 ActionBeginTransactionResult, ActionCancelQueryRequest, ActionCancelQueryResult,
17 ActionClosePreparedStatementRequest, ActionCreatePreparedStatementRequest,
18 ActionCreatePreparedStatementResult, ActionCreatePreparedSubstraitPlanRequest,
19 ActionEndSavepointRequest, ActionEndTransactionRequest, Any, CommandGetCatalogs,
20 CommandGetCrossReference, CommandGetDbSchemas, CommandGetExportedKeys, CommandGetImportedKeys,
21 CommandGetPrimaryKeys, CommandGetSqlInfo, CommandGetTableTypes, CommandGetTables,
22 CommandGetXdbcTypeInfo, CommandPreparedStatementQuery, CommandPreparedStatementUpdate,
23 CommandStatementIngest, CommandStatementQuery, CommandStatementSubstraitPlan,
24 CommandStatementUpdate, ProstMessageExt, SqlInfo, TicketStatementQuery,
25 server::FlightSqlService,
26};
27use arrow_flight::{
28 Action, FlightDescriptor, FlightEndpoint, FlightInfo, HandshakeRequest, HandshakeResponse,
29 Ticket, flight_service_server::FlightService,
30};
31use core::str;
32use datafusion::arrow::array::{RecordBatch, StringArray};
33use datafusion::arrow::datatypes::{DataType, Field, Schema};
34use datafusion::arrow::ipc::writer::StreamWriter;
35use datafusion::error::DataFusionError;
36use datafusion::physical_plan::{ExecutionPlan, execute_stream};
37use futures::StreamExt;
38use futures::{Stream, TryStreamExt};
39use micromegas_analytics::lakehouse::lakehouse_context::LakehouseContext;
40use micromegas_analytics::lakehouse::partition_cache::QueryPartitionProvider;
41use micromegas_analytics::lakehouse::query::make_session_context;
42use micromegas_analytics::lakehouse::query_deny_list::{
43 QueryAttribution, fingerprint_of, skip_for_admin_recovery,
44};
45use micromegas_analytics::lakehouse::read_scope::{CallerContext, IsolationConfig, ReadScope};
46use micromegas_analytics::lakehouse::runtime::scoped_runtime;
47use micromegas_analytics::lakehouse::scoped_memory_pool::ScopedMemoryPool;
48use micromegas_analytics::lakehouse::session_configurator::SessionConfigurator;
49use micromegas_analytics::lakehouse::view_definition::{
50 build_sql_batch_view, validate_view_definition,
51};
52use micromegas_analytics::lakehouse::view_definition_store::{
53 delete_tx, list_tx, partition_insert_range, upsert_tx,
54};
55use micromegas_analytics::lakehouse::view_registry::{BuildPurpose, ViewRegistry};
56use micromegas_analytics::lakehouse::write_partition::{RetireMatch, retire_partitions};
57use micromegas_analytics::replication::bulk_ingest;
58use micromegas_analytics::response_writer::ResponseWriter;
59use micromegas_analytics::time::TimeRange;
60use micromegas_auth::policy::{ReadPolicy, caller_selectors};
61use micromegas_auth::types::{AuthContext, ProviderUnavailable};
62use micromegas_auth::user_attribution::{is_admin, validate_and_resolve_user_attribution_grpc};
63use micromegas_ingestion::remote_data_lake::acquire_lock;
64use micromegas_tracing::prelude::*;
65use once_cell::sync::Lazy;
66use prost::Message;
67use std::pin::Pin;
68use std::str::FromStr;
69use std::sync::Arc;
70use std::task::{Context, Poll};
71use std::time::Instant;
72use tonic::metadata::MetadataMap;
73use tonic::{Request, Response, Status, Streaming};
74use uuid::Uuid;
75
76use super::view_ddl::{ViewDdl, authorize_view_ddl, parse_view_ddl};
77
78const VIEW_DDL_ADVISORY_LOCK_KEY: i64 = 2;
82
83type FlightDataStream =
84 Pin<Box<dyn Stream<Item = Result<arrow_flight::FlightData, Status>> + Send>>;
85
86macro_rules! status {
87 ($desc:expr, $err:expr) => {
88 Status::internal(format!("{}: {} at {}:{}", $desc, $err, file!(), line!()))
89 };
90}
91
92macro_rules! api_entry_not_implemented {
93 () => {{
94 let function_name = micromegas_tracing::__function_name!();
95 error!("not implemented: {function_name}");
96 Err(Status::unimplemented(format!(
97 "{}:{} not implemented: {function_name}",
98 file!(),
99 line!()
100 )))
101 }};
102}
103
104macro_rules! client_input_error {
110 ($desc:expr, $err:expr) => {
111 Status::invalid_argument(format!("{}: {}", $desc, $err))
112 };
113}
114
115pub fn classify_datafusion_error(err: &DataFusionError) -> tonic::Code {
121 use datafusion::error::DataFusionError as DFE;
122 match err.find_root() {
123 DFE::SQL(..)
124 | DFE::Plan(_)
125 | DFE::SchemaError(..)
126 | DFE::Execution(_)
127 | DFE::Configuration(_) => tonic::Code::InvalidArgument,
128 DFE::ResourcesExhausted(_) => tonic::Code::ResourceExhausted,
129 DFE::NotImplemented(_) => tonic::Code::Unimplemented,
130 _ => tonic::Code::Internal,
131 }
132}
133
134pub fn error_class(code: tonic::Code) -> &'static str {
142 match code {
143 tonic::Code::InvalidArgument | tonic::Code::Unimplemented => "user",
144 tonic::Code::ResourceExhausted => "resource",
145 _ => "internal",
146 }
147}
148
149pub const MAX_PLAN_CHARS: usize = 2000;
153
154fn truncate_plan_text(text: &str) -> String {
160 match text.char_indices().nth(MAX_PLAN_CHARS) {
161 None => text.to_string(),
162 Some((i, _)) => format!("{}... (truncated)", &text[..i]),
163 }
164}
165
166pub fn build_log_line(
174 desc: &str,
175 err: &DataFusionError,
176 query_id: &str,
177 plan: Option<&Arc<dyn ExecutionPlan>>,
178) -> String {
179 let mut full = format!("{desc}: {err} (query_id={query_id})");
180 if let Some(plan) = plan {
181 let plan_text = format!(
182 "{}",
183 datafusion::physical_plan::displayable(plan.as_ref()).indent(true)
184 );
185 full.push_str(&format!(
186 "\nphysical plan:\n{}",
187 truncate_plan_text(&plan_text)
188 ));
189 }
190 full
191}
192
193#[allow(clippy::too_many_arguments)]
198pub fn build_denial_warn_line(
199 rule_id: &str,
200 reason: &str,
201 sql_hash: &str,
202 user: &str,
203 email: &str,
204 client: &str,
205 entrypoint: &str,
206 client_ip: &str,
207 query_id: &str,
208) -> String {
209 format!(
210 "query denied rule_id={rule_id} reason={reason:?} sql_hash={sql_hash} user={user} \
211 email={email} client={client} entrypoint={entrypoint} client_ip={client_ip} \
212 query_id={query_id}"
213 )
214}
215
216fn error_or_warn_log(
220 code: tonic::Code,
221 desc: &str,
222 err: &DataFusionError,
223 query_id: &str,
224 plan: Option<&Arc<dyn ExecutionPlan>>,
225) {
226 let full = build_log_line(desc, err, query_id, plan);
227 match error_class(code) {
228 "internal" => error!("{full}"),
229 _ => warn!("{full}"),
230 }
231}
232
233pub fn client_error(
242 desc: &str,
243 err: DataFusionError,
244 query_id: &str,
245 plan: Option<&Arc<dyn ExecutionPlan>>,
246) -> Status {
247 let code = classify_datafusion_error(&err);
248 let mut msg = format!("{desc}: {}", err.find_root().strip_backtrace());
249 let mut has_span = false;
250 if let Some(diag) = err.diagnostic() {
251 if let Some(span) = diag.span {
252 has_span = true;
253 msg.push_str(&format!(
254 " (at line {}, column {})",
255 span.start.line, span.start.column
256 ));
257 }
258 for note in &diag.notes {
259 msg.push_str(&format!("\nnote: {}", note.message));
260 }
261 for help in &diag.helps {
262 msg.push_str(&format!("\nhelp: {}", help.message));
263 }
264 }
265 msg.push_str(&format!(" (query_id={query_id})"));
266 error_or_warn_log(
267 code,
268 desc,
269 &err,
270 query_id,
271 if has_span { None } else { plan },
272 );
273 Status::new(code, msg)
274}
275
276pub fn classify_flight_error(
284 err: FlightError,
285 query_id: &str,
286 plan: Option<&Arc<dyn ExecutionPlan>>,
287) -> Status {
288 match err {
289 FlightError::ExternalError(inner) => match inner.downcast::<DataFusionError>() {
290 Ok(df_err) => client_error("error building data stream", *df_err, query_id, plan),
291 Err(inner) => {
292 error!("error building data stream: {inner} (query_id={query_id})");
293 Status::internal(format!(
294 "error building data stream: {inner} (query_id={query_id})"
295 ))
296 }
297 },
298 other => {
299 error!("error building data stream: {other} (query_id={query_id})");
300 Status::internal(format!(
301 "error building data stream: {other} (query_id={query_id})"
302 ))
303 }
304 }
305}
306
307struct QueryAuditState {
314 query_id: String,
320 client_ip: String,
321 client: String,
322 agent: String,
323 entrypoint: String,
324 session: Option<String>,
325 notebook: Option<String>,
326 cell: Option<String>,
327 user: String,
328 email: String,
329 name: Option<String>,
330 service_account: bool,
331 service_account_name: Option<String>,
332 sql: String,
333 sql_hash: String,
336 range_begin: Option<String>,
337 range_end: Option<String>,
338 limit: Option<u64>,
339 context_init_ms: f64,
340 planning_ms: f64,
341 execution_ms: f64,
342 setup_ms: f64,
343 request_start: Instant,
344 plan: Option<Arc<dyn ExecutionPlan>>,
348 pool: Arc<ScopedMemoryPool>,
352}
353
354impl QueryAuditState {
355 fn emit(&self, status: &'static str, error: Option<String>, error_class: Option<&'static str>) {
365 let scan = match &self.plan {
366 Some(plan) => aggregate_scan_metrics(plan.as_ref()),
367 None => ScanMetrics {
368 output_rows: None,
369 bytes_scanned: 0,
370 spilled_bytes: 0,
371 spill_count: 0,
372 },
373 };
374 let peak_memory_bytes = self.pool.peak() as u64;
375 imetric!("query_peak_memory_bytes", "bytes", peak_memory_bytes);
376 let total_ms = self.request_start.elapsed().as_secs_f64() * 1000.0;
377 let record = QueryAuditRecord {
378 query_id: self.query_id.clone(),
379 client_ip: self.client_ip.clone(),
380 client: self.client.clone(),
381 agent: self.agent.clone(),
382 entrypoint: self.entrypoint.clone(),
383 session: self.session.clone(),
384 notebook: self.notebook.clone(),
385 cell: self.cell.clone(),
386 user: self.user.clone(),
387 email: self.email.clone(),
388 name: self.name.clone(),
389 service_account: self.service_account,
390 service_account_name: self.service_account_name.clone(),
391 sql: self.sql.clone(),
392 sql_hash: self.sql_hash.clone(),
393 range_begin: self.range_begin.clone(),
394 range_end: self.range_end.clone(),
395 limit: self.limit,
396 context_init_ms: self.context_init_ms,
397 planning_ms: self.planning_ms,
398 execution_ms: self.execution_ms,
399 setup_ms: self.setup_ms,
400 total_ms,
401 status,
402 error,
403 error_class,
404 output_rows: scan.output_rows,
405 bytes_scanned: scan.bytes_scanned,
406 peak_memory_bytes,
407 spilled_bytes: scan.spilled_bytes,
408 spill_count: scan.spill_count,
409 };
410 match serde_json::to_string(&record) {
411 Ok(json) => info!(target: "flightsql_query_audit", "{json}"),
412 Err(e) => warn!("failed to serialize query audit record: {e}"),
413 }
414 }
415
416 fn fail_with_class(&self, status: Status, class: &'static str) -> Status {
422 self.emit("error", Some(status.message().to_string()), Some(class));
423 status
424 }
425
426 fn fail(&self, status: Status) -> Status {
432 let class = error_class(status.code());
433 self.fail_with_class(status, class)
434 }
435}
436
437struct CompletionTrackedStream<S> {
439 inner: S,
440 start_time: i64,
441 completed: bool,
442 audit: Option<QueryAuditState>,
443}
444
445impl<S> CompletionTrackedStream<S> {
446 fn new(inner: S, start_time: i64, audit: QueryAuditState) -> Self {
447 Self {
448 inner,
449 start_time,
450 completed: false,
451 audit: Some(audit),
452 }
453 }
454}
455
456impl<S> Drop for CompletionTrackedStream<S> {
457 fn drop(&mut self) {
465 if let Some(state) = self.audit.take() {
466 state.emit("incomplete", None, None);
467 }
468 }
469}
470
471impl<S> Stream for CompletionTrackedStream<S>
472where
473 S: Stream<Item = Result<arrow_flight::FlightData, Status>> + Unpin + Send,
474{
475 type Item = Result<arrow_flight::FlightData, Status>;
476
477 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
478 match Pin::new(&mut self.inner).poll_next(cx) {
479 Poll::Ready(Some(result)) => {
480 if let Err(ref err) = result
485 && !self.completed
486 {
487 let class = error_class(err.code());
488 let total_duration = now() - self.start_time;
489 match class {
490 "internal" => {
491 imetric!("query_duration_with_error", "ticks", total_duration as u64);
492 imetric!("query_failed", "count", 1);
493 }
494 "resource" => {
495 imetric!("query_failed_resource", "count", 1);
496 }
497 _ => {
498 imetric!("query_failed_user", "count", 1);
499 }
500 }
501 self.completed = true;
502 if let Some(state) = self.audit.take() {
503 state.emit("error", Some(err.message().to_string()), Some(class));
504 }
505 }
506 Poll::Ready(Some(result))
507 }
508 Poll::Ready(None) => {
509 if !self.completed {
511 let total_duration = now() - self.start_time;
512 imetric!("query_duration_total", "ticks", total_duration as u64);
513 imetric!("query_completed_successfully", "count", 1);
514 self.completed = true;
515 if let Some(state) = self.audit.take() {
516 state.emit("ok", None, None);
517 }
518 }
519 Poll::Ready(None)
520 }
521 Poll::Pending => Poll::Pending,
522 }
523 }
524}
525
526static INSTANCE_SQL_DATA: Lazy<SqlInfoData> = Lazy::new(|| {
527 let mut builder = SqlInfoDataBuilder::new();
528 builder.append(SqlInfo::FlightSqlServerName, "Micromegas Flight SQL Server");
530 builder.append(SqlInfo::FlightSqlServerVersion, "1");
531 builder.append(SqlInfo::FlightSqlServerArrowVersion, "1.3");
533 builder.append(SqlInfo::SqlKeywords, SQL_INFO_SQL_KEYWORDS);
534 builder.append(SqlInfo::SqlNumericFunctions, SQL_INFO_NUMERIC_FUNCTIONS);
535 builder.append(SqlInfo::SqlStringFunctions, SQL_INFO_STRING_FUNCTIONS);
536 builder.append(SqlInfo::SqlSystemFunctions, SQL_INFO_SYSTEM_FUNCTIONS);
537 builder.append(SqlInfo::SqlDatetimeFunctions, SQL_INFO_DATE_TIME_FUNCTIONS);
538 builder.build().unwrap()
539});
540
541#[derive(Clone)]
543pub struct FlightSqlServiceImpl {
544 lakehouse: Arc<LakehouseContext>,
545 part_provider: Arc<dyn QueryPartitionProvider>,
546 view_registry: Arc<ViewRegistry>,
547 session_configurator: Arc<dyn SessionConfigurator>,
548 read_policy: Arc<dyn ReadPolicy>,
549 isolation_config: Arc<IsolationConfig>,
550}
551
552impl FlightSqlServiceImpl {
553 pub fn new(
554 lakehouse: Arc<LakehouseContext>,
555 part_provider: Arc<dyn QueryPartitionProvider>,
556 view_registry: Arc<ViewRegistry>,
557 session_configurator: Arc<dyn SessionConfigurator>,
558 read_policy: Arc<dyn ReadPolicy>,
559 isolation_config: Arc<IsolationConfig>,
560 ) -> Self {
561 Self {
562 lakehouse,
563 part_provider,
564 view_registry,
565 session_configurator,
566 read_policy,
567 isolation_config,
568 }
569 }
570
571 async fn caller_context(
595 &self,
596 ext: &http::Extensions,
597 md: &MetadataMap,
598 ) -> Result<CallerContext, Status> {
599 let identity = validate_and_resolve_user_attribution_grpc(md)
604 .ok()
605 .map(|attr| attr.user_id);
606 let auth_ctx = ext.get::<AuthContext>();
610 let read_scope = match auth_ctx {
611 Some(auth_ctx) => match self.read_policy.resolve(auth_ctx).await {
612 Ok(audiences) => ReadScope::Audiences(audiences.into_inner()),
613 Err(e) => {
614 return Err(if e.downcast_ref::<ProviderUnavailable>().is_some() {
615 Status::unavailable(format!("read policy unavailable: {e:#}"))
616 } else {
617 Status::permission_denied(format!("read scope denied: {e:#}"))
618 });
619 }
620 },
621 None => ReadScope::All,
622 };
623 Ok(CallerContext {
624 read_scope,
625 is_admin: is_admin(md),
626 isolation_config: self.isolation_config.clone(),
630 identity,
631 grant_selectors: auth_ctx.map(caller_selectors).unwrap_or_default().into(),
635 })
636 }
637
638 fn should_preserve_dictionary(metadata: &MetadataMap) -> bool {
639 metadata
640 .get("preserve_dictionary")
641 .and_then(|v| v.to_str().ok())
642 .map(|s| s.eq_ignore_ascii_case("true"))
643 .unwrap_or(false)
644 }
645
646 fn optional_metadata(metadata: &MetadataMap, key: &str) -> Option<String> {
648 metadata
649 .get(key)
650 .and_then(|v| v.to_str().ok())
651 .filter(|s| !s.is_empty())
652 .map(|s| s.to_string())
653 }
654
655 #[span_fn]
656 async fn execute_query(
657 &self,
658 ticket_stmt: TicketStatementQuery,
659 metadata: &MetadataMap,
660 extensions: &http::Extensions,
661 client_ip: &str,
662 ) -> Result<Response<FlightDataStream>, Status> {
663 let query_id = Uuid::new_v4().to_string();
667 let begin_request = now();
668 let request_start = Instant::now();
669 let sql = std::str::from_utf8(&ticket_stmt.statement_handle)
670 .map_err(|e| status!("Unable to parse query", e))?;
671
672 let mut begin = metadata.get("query_range_begin");
673 if let Some(s) = &begin
674 && s.is_empty()
675 {
676 begin = None;
677 }
678 let mut end = metadata.get("query_range_end");
679 if let Some(s) = &end
680 && s.is_empty()
681 {
682 end = None;
683 }
684 let query_range = if begin.is_some() && end.is_some() {
685 let begin_datetime =
686 chrono::DateTime::parse_from_rfc3339(begin.unwrap().to_str().map_err(|e| {
687 client_input_error!("Unable to convert query_range_begin to string", e)
688 })?)
689 .map_err(|e| {
690 client_input_error!(
691 "Unable to parse query_range_begin as a rfc3339 datetime",
692 e
693 )
694 })?;
695 let end_datetime =
696 chrono::DateTime::parse_from_rfc3339(end.unwrap().to_str().map_err(|e| {
697 client_input_error!("Unable to convert query_range_end to string", e)
698 })?)
699 .map_err(|e| {
700 client_input_error!("Unable to parse query_range_end as a rfc3339 datetime", e)
701 })?;
702 Some(TimeRange::new(begin_datetime.into(), end_datetime.into()))
703 } else {
704 None
705 };
706
707 let attr = validate_and_resolve_user_attribution_grpc(metadata).map_err(|e| *e)?;
709
710 let client_type = metadata
711 .get("x-client-type")
712 .and_then(|v| v.to_str().ok())
713 .unwrap_or("unknown");
714 let client_agent = metadata
715 .get("x-client-agent")
716 .and_then(|v| v.to_str().ok())
717 .unwrap_or("unknown");
718 let client_entrypoint = metadata
719 .get("x-client-entrypoint")
720 .and_then(|v| v.to_str().ok())
721 .unwrap_or("unknown");
722 let client_session = Self::optional_metadata(metadata, "x-client-session");
723 let client_notebook = Self::optional_metadata(metadata, "x-client-notebook");
724 let client_cell = Self::optional_metadata(metadata, "x-client-cell");
725
726 let user_name_display = attr.user_name.as_deref().unwrap_or("");
727
728 if let Some(service_account_name) = &attr.service_account {
730 info!(
731 "execute_query range={query_range:?} sql={sql:?} limit={:?} user={} email={} name={user_name_display:?} service_account={service_account_name} client={client_type} agent={client_agent} entrypoint={client_entrypoint} notebook={client_notebook:?} cell={client_cell:?} client_ip={client_ip}",
732 metadata.get("limit"),
733 attr.user_id,
734 attr.user_email
735 );
736 } else {
737 info!(
738 "execute_query range={query_range:?} sql={sql:?} limit={:?} user={} email={} name={user_name_display:?} client={client_type} agent={client_agent} entrypoint={client_entrypoint} notebook={client_notebook:?} cell={client_cell:?} client_ip={client_ip}",
739 metadata.get("limit"),
740 attr.user_id,
741 attr.user_email
742 );
743 }
744
745 let scoped_pool = Arc::new(ScopedMemoryPool::new(
752 self.lakehouse.runtime().memory_pool.clone(),
753 ));
754
755 let sql_hash = fingerprint_of(sql);
762
763 let mut audit_state = QueryAuditState {
764 query_id: query_id.clone(),
765 client_ip: client_ip.to_string(),
766 client: client_type.to_string(),
767 agent: client_agent.to_string(),
768 entrypoint: client_entrypoint.to_string(),
769 session: client_session,
770 notebook: client_notebook,
771 cell: client_cell,
772 user: attr.user_id.clone(),
773 email: attr.user_email.clone(),
774 name: attr.user_name.clone(),
775 service_account: attr.service_account.is_some(),
776 service_account_name: attr.service_account.clone(),
777 sql: sql.to_string(),
778 sql_hash: sql_hash.clone(),
779 range_begin: query_range.as_ref().map(|r| r.begin.to_rfc3339()),
780 range_end: query_range.as_ref().map(|r| r.end.to_rfc3339()),
781 limit: None,
782 context_init_ms: 0.0,
783 planning_ms: 0.0,
784 execution_ms: 0.0,
785 setup_ms: 0.0,
786 request_start,
787 plan: None,
788 pool: scoped_pool.clone(),
789 };
790
791 if let Some(rule) = self.lakehouse.query_denials().check(&QueryAttribution {
795 user_id: &attr.user_id,
796 email: &attr.user_email,
797 service_account: attr.service_account.as_deref(),
798 client: client_type,
799 agent: client_agent,
800 entrypoint: client_entrypoint,
801 session: audit_state.session.as_deref(),
802 notebook: audit_state.notebook.as_deref(),
803 cell: audit_state.cell.as_deref(),
804 client_ip,
805 sql,
806 sql_hash: &sql_hash,
807 }) && !skip_for_admin_recovery(sql, is_admin(metadata))
808 {
809 let status = Status::resource_exhausted(format!(
810 "query denied by rule {} (reason: {}); ask an admin to lift it with \
811 remove_query_denial('{}'); query_id={query_id}",
812 rule.row.rule_id, rule.row.reason, rule.row.rule_id
813 ));
814 warn!(
815 "{}",
816 build_denial_warn_line(
817 &rule.row.rule_id.to_string(),
818 &rule.row.reason,
819 &sql_hash,
820 &attr.user_id,
821 &attr.user_email,
822 client_type,
823 client_entrypoint,
824 client_ip,
825 &query_id,
826 )
827 );
828 imetric!("query_denied", "count", rule_id = %rule.row.rule_id, 1_u64);
829 rule.record_hit();
830 return Err(audit_state.fail_with_class(status, "denied"));
831 }
832
833 let session_begin = now();
841 let session_begin_instant = Instant::now();
842 let caller = self
843 .caller_context(extensions, metadata)
844 .await
845 .map_err(|status| audit_state.fail(status))?;
846
847 match parse_view_ddl(sql) {
850 Ok(Some(ddl)) => {
851 return self
852 .execute_view_ddl(ddl, &caller, begin_request, audit_state)
853 .await;
854 }
855 Ok(None) => {}
856 Err(e) => {
857 return Err(audit_state.fail(client_input_error!("error parsing view DDL", e)));
858 }
859 }
860
861 let scoped_env = scoped_runtime(self.lakehouse.runtime(), scoped_pool.clone())
866 .map_err(|e| audit_state.fail(status!("error building scoped runtime", e)))?;
867 let lakehouse = self.lakehouse.with_runtime(scoped_env);
868
869 let ctx = make_session_context(
871 lakehouse,
872 self.part_provider.clone(),
873 query_range,
874 self.view_registry.current(),
875 self.session_configurator.clone(),
876 caller,
877 )
878 .await
879 .map_err(|e| audit_state.fail(status!("error in make_session_context", e)))?;
880 let context_init_duration = now() - session_begin;
881 audit_state.context_init_ms = session_begin_instant.elapsed().as_secs_f64() * 1000.0;
882
883 let planning_begin = now();
885 let planning_begin_instant = Instant::now();
886 let mut df = ctx.sql(sql).await.map_err(|e| {
887 audit_state.fail(client_error("error building dataframe", e, &query_id, None))
888 })?;
889 let planning_duration = now() - planning_begin;
890 audit_state.planning_ms = planning_begin_instant.elapsed().as_secs_f64() * 1000.0;
891
892 if let Some(limit_str) = metadata.get("limit") {
893 let parsed_limit: usize = usize::from_str(limit_str.to_str().map_err(|e| {
894 audit_state.fail(client_input_error!("error converting limit to str", e))
895 })?)
896 .map_err(|e| audit_state.fail(client_input_error!("error parsing limit", e)))?;
897 audit_state.limit = Some(parsed_limit as u64);
898 df = df.limit(0, Some(parsed_limit)).map_err(|e| {
899 audit_state.fail(client_error(
900 "error building dataframe with limit",
901 e,
902 &query_id,
903 None,
904 ))
905 })?;
906 }
907
908 let execution_begin = now();
912 let execution_begin_instant = Instant::now();
913 let schema = Arc::new(df.schema().as_arrow().clone());
914 let task_ctx = Arc::new(df.task_ctx());
915 let plan = df.create_physical_plan().await.map_err(|e| {
916 audit_state.fail(client_error(
917 "error creating physical plan",
918 e,
919 &query_id,
920 None,
921 ))
922 })?;
923 audit_state.plan = Some(plan.clone());
924 let plan_for_errors = plan.clone();
931 let query_id_for_stream = query_id.clone();
932 let stream = execute_stream(plan, task_ctx)
933 .map_err(|e| {
934 audit_state.fail(client_error(
935 "error executing plan",
936 e,
937 &query_id_for_stream,
938 Some(&plan_for_errors),
939 ))
940 })?
941 .map_err(|e| FlightError::ExternalError(Box::new(e)));
942 let builder = if Self::should_preserve_dictionary(metadata) {
943 FlightDataEncoderBuilder::new()
944 .with_schema(schema.clone())
945 .with_dictionary_handling(DictionaryHandling::Resend)
946 } else {
947 FlightDataEncoderBuilder::new().with_schema(schema.clone())
948 };
949 let flight_data_stream = builder.build(stream);
950 let execution_duration = now() - execution_begin;
951 audit_state.execution_ms = execution_begin_instant.elapsed().as_secs_f64() * 1000.0;
952
953 let total_setup_duration = now() - begin_request;
955 audit_state.setup_ms = request_start.elapsed().as_secs_f64() * 1000.0;
956
957 imetric!(
959 "context_init_duration",
960 "ticks",
961 context_init_duration as u64
962 );
963 imetric!("query_planning_duration", "ticks", planning_duration as u64);
964 imetric!(
965 "query_execution_duration",
966 "ticks",
967 execution_duration as u64
968 );
969 imetric!("query_setup_duration", "ticks", total_setup_duration as u64);
970
971 let instrumented_stream = flight_data_stream.map_err(move |e| {
976 classify_flight_error(e, &query_id_for_stream, Some(&plan_for_errors))
977 });
978 let completion_tracked_stream =
979 CompletionTrackedStream::new(instrumented_stream.boxed(), begin_request, audit_state);
980 Ok(Response::new(
981 Box::pin(completion_tracked_stream) as FlightDataStream
982 ))
983 }
984
985 async fn execute_view_ddl(
992 &self,
993 ddl: ViewDdl,
994 caller: &CallerContext,
995 begin_request: i64,
996 audit_state: QueryAuditState,
997 ) -> Result<Response<FlightDataStream>, Status> {
998 authorize_view_ddl(caller).map_err(|status| audit_state.fail(status))?;
999
1000 let pool = self.lakehouse.lake().db_pool.clone();
1001 let mut tx = pool
1002 .begin()
1003 .await
1004 .map_err(|e| audit_state.fail(status!("error starting view DDL transaction", e)))?;
1005 acquire_lock(&mut tx, VIEW_DDL_ADVISORY_LOCK_KEY)
1006 .await
1007 .map_err(|e| audit_state.fail(status!("error acquiring view DDL lock", e)))?;
1008 let pre_rows = list_tx(&mut tx)
1009 .await
1010 .map_err(|e| audit_state.fail(status!("error reading view definitions", e)))?;
1011
1012 let (view_set_name, status_text) = match ddl {
1013 ViewDdl::Create {
1014 name,
1015 or_replace,
1016 definition,
1017 sql,
1018 } => {
1019 let existing = pre_rows.iter().any(|r| r.view_set_name == name);
1020 if existing && !or_replace {
1021 return Err(audit_state.fail(Status::already_exists(format!(
1022 "materialized view '{name}' already exists; use CREATE OR REPLACE to \
1023 redefine it"
1024 ))));
1025 }
1026 let validation_rows: Vec<_> = pre_rows
1033 .iter()
1034 .filter(|r| r.view_set_name != name)
1035 .cloned()
1036 .collect();
1037 let (factory, _failed) = self
1041 .view_registry
1042 .build_from_rows(&validation_rows, BuildPurpose::Probe)
1043 .await
1044 .map_err(|e| {
1045 audit_state.fail(status!("error building validation factory", e))
1046 })?;
1047 let view = build_sql_batch_view(
1048 &definition,
1049 self.lakehouse.runtime().clone(),
1050 self.lakehouse.lake().clone(),
1051 factory.clone(),
1052 self.session_configurator.clone(),
1053 )
1054 .await
1055 .map_err(|e| {
1056 audit_state.fail(client_input_error!("error building materialized view", e))
1057 })?;
1058 validate_view_definition(
1059 &definition,
1060 &view,
1061 &factory,
1062 self.lakehouse.runtime().clone(),
1063 self.lakehouse.lake().clone(),
1064 self.session_configurator.clone(),
1065 )
1066 .await
1067 .map_err(|e| {
1068 audit_state.fail(client_input_error!("view definition failed validation", e))
1069 })?;
1070 let updated_by = caller.identity.as_deref().unwrap_or("unknown");
1071 upsert_tx(&mut tx, &definition, &sql, updated_by)
1072 .await
1073 .map_err(|e| audit_state.fail(status!("error upserting view definition", e)))?;
1074 (name, if existing { "replaced" } else { "created" })
1075 }
1076 ViewDdl::Drop { name, if_exists } => {
1077 let existing = pre_rows.iter().any(|r| r.view_set_name == name);
1078 if !existing {
1079 if if_exists {
1080 (name, "not_found")
1081 } else {
1082 return Err(audit_state.fail(Status::not_found(format!(
1083 "materialized view '{name}' does not exist"
1084 ))));
1085 }
1086 } else {
1087 let range = partition_insert_range(&mut tx, &name).await.map_err(|e| {
1088 audit_state.fail(status!("error reading partition insert range", e))
1089 })?;
1090 delete_tx(&mut tx, &name).await.map_err(|e| {
1091 audit_state.fail(status!("error deleting view definition", e))
1092 })?;
1093 if let Some((begin, end)) = range {
1094 let null_logger = Arc::new(ResponseWriter::new(None));
1095 retire_partitions(
1096 &mut tx,
1097 &name,
1098 "global",
1099 begin,
1100 end,
1101 RetireMatch::Containment,
1102 &[],
1103 null_logger,
1104 )
1105 .await
1106 .map_err(|e| audit_state.fail(status!("error retiring partitions", e)))?;
1107 }
1108 (name, "dropped")
1109 }
1110 }
1111 };
1112
1113 if status_text != "not_found" {
1118 let post_rows = list_tx(&mut tx)
1119 .await
1120 .map_err(|e| audit_state.fail(status!("error reading view definitions", e)))?;
1121 self.view_registry
1122 .check_dependents_survive(&pre_rows, &post_rows)
1123 .await
1124 .map_err(|e| {
1125 audit_state.fail(client_input_error!("view DDL refused", e))
1128 })?;
1129 }
1130
1131 tx.commit()
1132 .await
1133 .map_err(|e| audit_state.fail(status!("error committing view DDL transaction", e)))?;
1134
1135 if let Err(e) = self.view_registry.reload().await {
1140 warn!("view_ddl: inline reload after DDL failed: {e:#}");
1141 }
1142
1143 let schema = Arc::new(Schema::new(vec![
1144 Field::new("view_set_name", DataType::Utf8, false),
1145 Field::new("status", DataType::Utf8, false),
1146 ]));
1147 let batch = RecordBatch::try_new(
1148 schema.clone(),
1149 vec![
1150 Arc::new(StringArray::from(vec![view_set_name])),
1151 Arc::new(StringArray::from(vec![status_text.to_string()])),
1152 ],
1153 )
1154 .map_err(|e| audit_state.fail(status!("error building view DDL result batch", e)))?;
1155 let stream = FlightDataEncoderBuilder::new()
1156 .with_schema(schema)
1157 .build(futures::stream::once(async { Ok(batch) }))
1158 .map_err(Status::from);
1159 let completion_tracked_stream =
1160 CompletionTrackedStream::new(stream.boxed(), begin_request, audit_state);
1161 Ok(Response::new(
1162 Box::pin(completion_tracked_stream) as FlightDataStream
1163 ))
1164 }
1165}
1166
1167#[tonic::async_trait]
1168impl FlightSqlService for FlightSqlServiceImpl {
1169 type FlightService = FlightSqlServiceImpl;
1170
1171 async fn do_handshake(
1172 &self,
1173 _request: Request<Streaming<HandshakeRequest>>,
1174 ) -> Result<
1175 Response<Pin<Box<dyn Stream<Item = Result<HandshakeResponse, Status>> + Send>>>,
1176 Status,
1177 > {
1178 api_entry_not_implemented!()
1179 }
1180
1181 #[span_fn]
1182 async fn do_get_fallback(
1183 &self,
1184 request: Request<Ticket>,
1185 _message: Any,
1186 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1187 let client_ip = get_client_ip(request.metadata().as_ref(), request.extensions());
1188 let ticket_stmt = TicketStatementQuery::decode(request.get_ref().ticket.clone())
1189 .map_err(|e| status!("Could not read ticket", e))?;
1190 self.execute_query(
1191 ticket_stmt,
1192 request.metadata(),
1193 request.extensions(),
1194 &client_ip,
1195 )
1196 .await
1197 }
1198
1199 #[span_fn]
1200 async fn get_flight_info_statement(
1201 &self,
1202 query: CommandStatementQuery,
1203 _request: Request<FlightDescriptor>,
1204 ) -> Result<Response<FlightInfo>, Status> {
1205 let begin_request = now();
1206 info!("get_flight_info_statement {query:?} ");
1207 let CommandStatementQuery { query, .. } = query;
1208 let schema = Schema::empty();
1209 let ticket = TicketStatementQuery {
1210 statement_handle: query.into(),
1211 };
1212 let mut bytes: Vec<u8> = Vec::new();
1213 if ticket.encode(&mut bytes).is_ok() {
1214 let info = FlightInfo::new()
1215 .try_with_schema(&schema)
1216 .unwrap()
1217 .with_endpoint(FlightEndpoint::new().with_ticket(Ticket::new(bytes)));
1218 let duration = now() - begin_request;
1219 imetric!("request_duration", "ticks", duration as u64);
1220 Ok(Response::new(info))
1221 } else {
1222 error!("Error encoding ticket");
1223 Err(Status::internal("Error encoding ticket"))
1224 }
1225 }
1226
1227 async fn get_flight_info_substrait_plan(
1228 &self,
1229 _query: CommandStatementSubstraitPlan,
1230 _request: Request<FlightDescriptor>,
1231 ) -> Result<Response<FlightInfo>, Status> {
1232 api_entry_not_implemented!()
1233 }
1234
1235 async fn get_flight_info_prepared_statement(
1236 &self,
1237 _cmd: CommandPreparedStatementQuery,
1238 _request: Request<FlightDescriptor>,
1239 ) -> Result<Response<FlightInfo>, Status> {
1240 api_entry_not_implemented!()
1241 }
1242
1243 async fn get_flight_info_catalogs(
1244 &self,
1245 _query: CommandGetCatalogs,
1246 _request: Request<FlightDescriptor>,
1247 ) -> Result<Response<FlightInfo>, Status> {
1248 api_entry_not_implemented!()
1249 }
1250
1251 async fn get_flight_info_schemas(
1252 &self,
1253 _query: CommandGetDbSchemas,
1254 _request: Request<FlightDescriptor>,
1255 ) -> Result<Response<FlightInfo>, Status> {
1256 api_entry_not_implemented!()
1257 }
1258
1259 #[span_fn]
1260 async fn get_flight_info_tables(
1261 &self,
1262 query: CommandGetTables,
1263 request: Request<FlightDescriptor>,
1264 ) -> Result<Response<FlightInfo>, Status> {
1265 let begin_request = now();
1266 info!("get_flight_info_tables");
1267 let flight_descriptor = request.into_inner();
1268 let ticket = Ticket {
1269 ticket: query.as_any().encode_to_vec().into(),
1270 };
1271 let endpoint = FlightEndpoint::new().with_ticket(ticket);
1272 let flight_info = FlightInfo::new()
1273 .try_with_schema(&query.into_builder().schema())
1274 .map_err(|e| status!("Unable to encode schema", e))?
1275 .with_endpoint(endpoint)
1276 .with_descriptor(flight_descriptor);
1277 let duration = now() - begin_request;
1278 imetric!("request_duration", "ticks", duration as u64);
1279 Ok(tonic::Response::new(flight_info))
1280 }
1281
1282 async fn get_flight_info_table_types(
1283 &self,
1284 _query: CommandGetTableTypes,
1285 _request: Request<FlightDescriptor>,
1286 ) -> Result<Response<FlightInfo>, Status> {
1287 api_entry_not_implemented!()
1288 }
1289
1290 #[span_fn]
1291 async fn get_flight_info_sql_info(
1292 &self,
1293 query: CommandGetSqlInfo,
1294 request: Request<FlightDescriptor>,
1295 ) -> Result<Response<FlightInfo>, Status> {
1296 let begin_request = now();
1297 info!("get_flight_info_sql_info");
1298 let flight_descriptor = request.into_inner();
1299 let ticket = Ticket::new(query.as_any().encode_to_vec());
1300 let endpoint = FlightEndpoint::new().with_ticket(ticket);
1301 let flight_info = FlightInfo::new()
1302 .try_with_schema(query.into_builder(&INSTANCE_SQL_DATA).schema().as_ref())
1303 .map_err(|e| status!("Unable to encode schema", e))?
1304 .with_endpoint(endpoint)
1305 .with_descriptor(flight_descriptor);
1306 let duration = now() - begin_request;
1307 imetric!("request_duration", "ticks", duration as u64);
1308 Ok(tonic::Response::new(flight_info))
1309 }
1310
1311 async fn get_flight_info_primary_keys(
1312 &self,
1313 _query: CommandGetPrimaryKeys,
1314 _request: Request<FlightDescriptor>,
1315 ) -> Result<Response<FlightInfo>, Status> {
1316 api_entry_not_implemented!()
1317 }
1318
1319 async fn get_flight_info_exported_keys(
1320 &self,
1321 _query: CommandGetExportedKeys,
1322 _request: Request<FlightDescriptor>,
1323 ) -> Result<Response<FlightInfo>, Status> {
1324 api_entry_not_implemented!()
1325 }
1326
1327 async fn get_flight_info_imported_keys(
1328 &self,
1329 _query: CommandGetImportedKeys,
1330 _request: Request<FlightDescriptor>,
1331 ) -> Result<Response<FlightInfo>, Status> {
1332 api_entry_not_implemented!()
1333 }
1334
1335 async fn get_flight_info_cross_reference(
1336 &self,
1337 _query: CommandGetCrossReference,
1338 _request: Request<FlightDescriptor>,
1339 ) -> Result<Response<FlightInfo>, Status> {
1340 api_entry_not_implemented!()
1341 }
1342
1343 async fn get_flight_info_xdbc_type_info(
1344 &self,
1345 _query: CommandGetXdbcTypeInfo,
1346 _request: Request<FlightDescriptor>,
1347 ) -> Result<Response<FlightInfo>, Status> {
1348 api_entry_not_implemented!()
1349 }
1350
1351 #[span_fn]
1352 async fn do_get_statement(
1353 &self,
1354 ticket: TicketStatementQuery,
1355 request: Request<Ticket>,
1356 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1357 let client_ip = get_client_ip(request.metadata().as_ref(), request.extensions());
1358 self.execute_query(ticket, request.metadata(), request.extensions(), &client_ip)
1359 .await
1360 }
1361
1362 async fn do_get_prepared_statement(
1363 &self,
1364 _query: CommandPreparedStatementQuery,
1365 _request: Request<Ticket>,
1366 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1367 api_entry_not_implemented!()
1368 }
1369
1370 async fn do_get_catalogs(
1371 &self,
1372 _query: CommandGetCatalogs,
1373 _request: Request<Ticket>,
1374 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1375 api_entry_not_implemented!()
1376 }
1377
1378 async fn do_get_schemas(
1379 &self,
1380 _query: CommandGetDbSchemas,
1381 _request: Request<Ticket>,
1382 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1383 api_entry_not_implemented!()
1384 }
1385
1386 #[span_fn]
1387 async fn do_get_tables(
1388 &self,
1389 query: CommandGetTables,
1390 _request: Request<Ticket>,
1391 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1392 let begin_request = now();
1393 info!("do_get_tables {query:?}");
1394 let mut builder = query.into_builder();
1395 let view_factory = self.view_registry.current();
1396 for view in view_factory.get_global_views() {
1397 let catalog_name = "";
1398 let schema_name = "";
1399 builder
1400 .append(
1401 catalog_name,
1402 schema_name,
1403 &*view.get_view_set_name(),
1404 "table",
1405 &view.get_file_schema(),
1406 )
1407 .map_err(Status::from)?;
1408 }
1409 let schema = builder.schema();
1410 let batch = builder.build();
1411 let stream = FlightDataEncoderBuilder::new()
1412 .with_schema(schema)
1413 .build(futures::stream::once(async { batch }))
1414 .map_err(Status::from);
1415 let duration = now() - begin_request;
1416 imetric!("request_duration", "ticks", duration as u64);
1417 Ok(Response::new(Box::pin(stream)))
1418 }
1419
1420 async fn do_get_table_types(
1421 &self,
1422 _query: CommandGetTableTypes,
1423 _request: Request<Ticket>,
1424 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1425 api_entry_not_implemented!()
1426 }
1427
1428 #[span_fn]
1429 async fn do_get_sql_info(
1430 &self,
1431 query: CommandGetSqlInfo,
1432 _request: Request<Ticket>,
1433 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1434 info!("do_get_sql_info");
1435 let builder = query.into_builder(&INSTANCE_SQL_DATA);
1436 let schema = builder.schema();
1437 let batch = builder.build();
1438 let stream = FlightDataEncoderBuilder::new()
1439 .with_schema(schema)
1440 .build(futures::stream::once(async { batch }))
1441 .map_err(Status::from);
1442 Ok(Response::new(Box::pin(stream)))
1443 }
1444
1445 async fn do_get_primary_keys(
1446 &self,
1447 _query: CommandGetPrimaryKeys,
1448 _request: Request<Ticket>,
1449 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1450 api_entry_not_implemented!()
1451 }
1452
1453 async fn do_get_exported_keys(
1454 &self,
1455 _query: CommandGetExportedKeys,
1456 _request: Request<Ticket>,
1457 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1458 api_entry_not_implemented!()
1459 }
1460
1461 async fn do_get_imported_keys(
1462 &self,
1463 _query: CommandGetImportedKeys,
1464 _request: Request<Ticket>,
1465 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1466 api_entry_not_implemented!()
1467 }
1468
1469 async fn do_get_cross_reference(
1470 &self,
1471 _query: CommandGetCrossReference,
1472 _request: Request<Ticket>,
1473 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1474 api_entry_not_implemented!()
1475 }
1476
1477 async fn do_get_xdbc_type_info(
1478 &self,
1479 _query: CommandGetXdbcTypeInfo,
1480 _request: Request<Ticket>,
1481 ) -> Result<Response<<Self as FlightService>::DoGetStream>, Status> {
1482 api_entry_not_implemented!()
1483 }
1484
1485 async fn do_put_statement_update(
1486 &self,
1487 _ticket: CommandStatementUpdate,
1488 _request: Request<PeekableFlightDataStream>,
1489 ) -> Result<i64, Status> {
1490 api_entry_not_implemented!()
1491 }
1492
1493 #[span_fn]
1504 async fn do_put_statement_ingest(
1505 &self,
1506 command: CommandStatementIngest,
1507 request: Request<PeekableFlightDataStream>,
1508 ) -> Result<i64, Status> {
1509 let table_name = command.table;
1510 info!("do_put_statement_ingest table_name={table_name}");
1511 if !is_admin(request.metadata()) {
1512 return Err(Status::permission_denied(
1513 "bulk_ingest requires admin privileges",
1514 ));
1515 }
1516 let stream = FlightRecordBatchStream::new_from_flight_data(
1517 request.into_inner().map_err(|e| e.into()),
1518 );
1519 bulk_ingest(self.lakehouse.lake().clone(), &table_name, stream)
1520 .await
1521 .map_err(|e| {
1522 let msg = format!("error ingesting into {table_name}: {e:?}");
1523 error!("{msg}");
1524 status!(msg, e)
1525 })
1526 }
1527
1528 async fn do_put_substrait_plan(
1529 &self,
1530 _ticket: CommandStatementSubstraitPlan,
1531 _request: Request<PeekableFlightDataStream>,
1532 ) -> Result<i64, Status> {
1533 api_entry_not_implemented!()
1534 }
1535
1536 async fn do_put_prepared_statement_query(
1537 &self,
1538 _query: CommandPreparedStatementQuery,
1539 _request: Request<PeekableFlightDataStream>,
1540 ) -> Result<DoPutPreparedStatementResult, Status> {
1541 api_entry_not_implemented!()
1542 }
1543
1544 async fn do_put_prepared_statement_update(
1545 &self,
1546 _query: CommandPreparedStatementUpdate,
1547 _request: Request<PeekableFlightDataStream>,
1548 ) -> Result<i64, Status> {
1549 api_entry_not_implemented!()
1550 }
1551
1552 #[span_fn]
1553 async fn do_action_create_prepared_statement(
1554 &self,
1555 query: ActionCreatePreparedStatementRequest,
1556 request: Request<Action>,
1557 ) -> Result<ActionCreatePreparedStatementResult, Status> {
1558 info!("do_action_create_prepared_statement query={}", &query.query);
1559
1560 if parse_view_ddl(&query.query)
1564 .map_err(|e| client_input_error!("error parsing view DDL", e))?
1565 .is_some()
1566 {
1567 return Err(Status::invalid_argument(
1568 "CREATE/DROP MATERIALIZED VIEW must be executed directly, not prepared",
1569 ));
1570 }
1571
1572 let caller = self
1574 .caller_context(request.extensions(), request.metadata())
1575 .await?;
1576 let ctx = make_session_context(
1577 self.lakehouse.clone(),
1578 self.part_provider.clone(),
1579 None,
1580 self.view_registry.current(),
1581 self.session_configurator.clone(),
1582 caller,
1583 )
1584 .await
1585 .map_err(|e| status!("error in make_session_context", e))?;
1586
1587 let local_query_id = Uuid::new_v4().to_string();
1591 let df = ctx
1592 .sql(&query.query)
1593 .await
1594 .map_err(|e| client_error("error building dataframe", e, &local_query_id, None))?;
1595 let schema = df.schema().as_arrow();
1596 let mut schema_buffer = Vec::new();
1597 let mut writer = StreamWriter::try_new(&mut schema_buffer, schema)
1598 .map_err(|e| status!("error writing schema to in-memory buffer", e))?;
1599 writer
1600 .finish()
1601 .map_err(|e| status!("error closing arrow ipc stream writer", e))?;
1602 let result = ActionCreatePreparedStatementResult {
1606 prepared_statement_handle: query.query.into(),
1607 dataset_schema: schema_buffer.into(),
1608 parameter_schema: "".into(),
1609 };
1610 Ok(result)
1611 }
1612
1613 async fn do_action_close_prepared_statement(
1614 &self,
1615 _query: ActionClosePreparedStatementRequest,
1616 _request: Request<Action>,
1617 ) -> Result<(), Status> {
1618 info!("do_action_close_prepared_statement");
1619 Ok(())
1620 }
1621
1622 async fn do_action_create_prepared_substrait_plan(
1623 &self,
1624 _query: ActionCreatePreparedSubstraitPlanRequest,
1625 _request: Request<Action>,
1626 ) -> Result<ActionCreatePreparedStatementResult, Status> {
1627 api_entry_not_implemented!()
1628 }
1629
1630 async fn do_action_begin_transaction(
1631 &self,
1632 _query: ActionBeginTransactionRequest,
1633 _request: Request<Action>,
1634 ) -> Result<ActionBeginTransactionResult, Status> {
1635 api_entry_not_implemented!()
1636 }
1637
1638 async fn do_action_end_transaction(
1639 &self,
1640 _query: ActionEndTransactionRequest,
1641 _request: Request<Action>,
1642 ) -> Result<(), Status> {
1643 api_entry_not_implemented!()
1644 }
1645
1646 async fn do_action_begin_savepoint(
1647 &self,
1648 _query: ActionBeginSavepointRequest,
1649 _request: Request<Action>,
1650 ) -> Result<ActionBeginSavepointResult, Status> {
1651 api_entry_not_implemented!()
1652 }
1653
1654 async fn do_action_end_savepoint(
1655 &self,
1656 _query: ActionEndSavepointRequest,
1657 _request: Request<Action>,
1658 ) -> Result<(), Status> {
1659 api_entry_not_implemented!()
1660 }
1661
1662 async fn do_action_cancel_query(
1663 &self,
1664 _query: ActionCancelQueryRequest,
1665 _request: Request<Action>,
1666 ) -> Result<ActionCancelQueryResult, Status> {
1667 api_entry_not_implemented!()
1668 }
1669
1670 async fn register_sql_info(&self, _id: i32, _result: &SqlInfo) {
1671 info!("register_sql_info");
1672 }
1673}