Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8127c7bb1b | ||
|
|
7437908baf | ||
|
|
9eb46cb5d3 | ||
|
|
38a70128b0 | ||
|
|
c58ce52b33 | ||
|
|
c82813185f | ||
|
|
a96681e9d6 | ||
|
|
4df6c40034 | ||
|
|
089d728cc7 | ||
|
|
aca3d718b5 | ||
|
|
8a6a584cf3 | ||
|
|
00ed0cf796 | ||
|
|
7e54b2fe43 | ||
|
|
84871faad4 | ||
|
|
bcb433d7b2 | ||
|
|
7d1b130b68 | ||
|
|
24c2376ea1 | ||
|
|
810ef5fc10 | ||
|
|
fe246b1fe6 | ||
|
|
de42bb48aa | ||
|
|
17495c49ac | ||
|
|
0e3a7a06a3 | ||
|
|
e0ee48eb9c | ||
|
|
d2053b1d5a | ||
|
|
fbe8e53858 | ||
|
|
8fe2581b3f | ||
|
|
60cc0e562e | ||
|
|
26898d474f | ||
|
|
2311fbaa3b | ||
|
|
be99cd9423 | ||
|
|
a3dd6fa95b | ||
|
|
433d87c96d | ||
|
|
aff4383671 | ||
|
|
b7c8f6b1a2 | ||
|
|
3443839ba4 | ||
|
|
6c31d48f3b | ||
|
|
1770292fd8 | ||
|
|
afdd5c5740 | ||
|
|
11487f0833 | ||
|
|
4d5d22d0c2 | ||
|
|
314a957922 | ||
|
|
4c57b562e6 | ||
|
|
a757acf51c | ||
|
|
f4a23be1a2 | ||
|
|
93c67ffa14 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
.env
|
||||
/tantivy_indexes
|
||||
server/tantivy_indexes
|
||||
steel_decimal/tests/property_tests.proptest-regressions
|
||||
|
||||
888
Cargo.lock
generated
888
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -4,14 +4,14 @@ resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
# TODO: idk how to do the name, fix later
|
||||
# name = "Multieko2"
|
||||
# name = "komp_ac"
|
||||
version = "0.3.13"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Filip Priečinský <filippriec@gmail.com>"]
|
||||
description = "Poriadny uctovnicky software."
|
||||
readme = "README.md"
|
||||
repository = "https://gitlab.com/filipriec/multieko2"
|
||||
repository = "https://gitlab.com/filipriec/komp_ac"
|
||||
categories = ["command-line-interface"]
|
||||
|
||||
# [workspace.metadata]
|
||||
@@ -40,4 +40,10 @@ tracing = "0.1.41"
|
||||
# Search crate
|
||||
tantivy = "0.24.1"
|
||||
|
||||
# Steel_decimal crate
|
||||
rust_decimal = { version = "1.37.2", features = ["maths", "serde"] }
|
||||
rust_decimal_macros = "1.37.1"
|
||||
thiserror = "2.0.12"
|
||||
regex = "1.11.1"
|
||||
|
||||
common = { path = "./common" }
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::config::colors::themes::Theme;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::state::pages::admin::AdminState;
|
||||
use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
||||
use common::proto::komp_ac::table_definition::ProfileTreeResponse;
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::Style,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::state::pages::form::FormState;
|
||||
use common::proto::multieko2::search::search_response::Hit;
|
||||
use common::proto::komp_ac::search::search_response::Hit;
|
||||
use ratatui::{
|
||||
layout::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn render_status_line(
|
||||
}
|
||||
|
||||
// --- The normal status line rendering logic (unchanged) ---
|
||||
let program_info = format!("multieko2 v{}", env!("CARGO_PKG_VERSION"));
|
||||
let program_info = format!("komp_ac v{}", env!("CARGO_PKG_VERSION"));
|
||||
let mode_text = if is_edit_mode { "[EDIT]" } else { "[READ-ONLY]" };
|
||||
|
||||
let home_dir = dirs::home_dir()
|
||||
|
||||
@@ -6,7 +6,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
use crate::config::colors::themes::Theme;
|
||||
use common::proto::multieko2::table_definition::{ProfileTreeResponse};
|
||||
use common::proto::komp_ac::table_definition::{ProfileTreeResponse};
|
||||
use ratatui::text::{Span, Line};
|
||||
use crate::components::utils::text::truncate_string;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn render_intro(f: &mut Frame, intro_state: &IntroState, area: Rect, theme:
|
||||
|
||||
// Title
|
||||
let title = Line::from(vec![
|
||||
Span::styled("multieko2", Style::default().fg(theme.highlight)),
|
||||
Span::styled("komp_ac", Style::default().fg(theme.highlight)),
|
||||
Span::styled(" v", Style::default().fg(theme.fg)),
|
||||
Span::styled(env!("CARGO_PKG_VERSION"), Style::default().fg(theme.secondary)),
|
||||
]);
|
||||
|
||||
@@ -9,7 +9,7 @@ use tracing::{error, info};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
pub const APP_NAME: &str = "multieko2_client";
|
||||
pub const APP_NAME: &str = "komp_ac_client";
|
||||
pub const TOKEN_FILE_NAME: &str = "auth.token";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::state::pages::{
|
||||
form::FormState,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use common::proto::multieko2::search::search_response::Hit;
|
||||
use common::proto::komp_ac::search::search_response::Hit;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, info};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use anyhow::Result;
|
||||
use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
||||
use common::proto::komp_ac::table_definition::ProfileTreeResponse;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ use crate::tui::{
|
||||
use crate::ui::handlers::context::UiContext;
|
||||
use crate::ui::handlers::rat_state::UiStateHandler;
|
||||
use anyhow::Result;
|
||||
use common::proto::multieko2::search::search_response::Hit;
|
||||
use common::proto::komp_ac::search::search_response::Hit;
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/auth.rs
|
||||
use tonic::transport::Channel;
|
||||
use common::proto::multieko2::auth::{
|
||||
use common::proto::komp_ac::auth::{
|
||||
auth_service_client::AuthServiceClient,
|
||||
LoginRequest, LoginResponse,
|
||||
RegisterRequest, AuthResponse,
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// src/services/grpc_client.rs
|
||||
|
||||
use common::proto::multieko2::common::Empty;
|
||||
use common::proto::multieko2::table_structure::table_structure_service_client::TableStructureServiceClient;
|
||||
use common::proto::multieko2::table_structure::{GetTableStructureRequest, TableStructureResponse};
|
||||
use common::proto::multieko2::table_definition::{
|
||||
use common::proto::komp_ac::common::Empty;
|
||||
use common::proto::komp_ac::table_structure::table_structure_service_client::TableStructureServiceClient;
|
||||
use common::proto::komp_ac::table_structure::{GetTableStructureRequest, TableStructureResponse};
|
||||
use common::proto::komp_ac::table_definition::{
|
||||
table_definition_client::TableDefinitionClient,
|
||||
PostTableDefinitionRequest, ProfileTreeResponse, TableDefinitionResponse,
|
||||
};
|
||||
use common::proto::multieko2::table_script::{
|
||||
use common::proto::komp_ac::table_script::{
|
||||
table_script_client::TableScriptClient,
|
||||
PostTableScriptRequest, TableScriptResponse,
|
||||
};
|
||||
use common::proto::multieko2::tables_data::{
|
||||
use common::proto::komp_ac::tables_data::{
|
||||
tables_data_client::TablesDataClient,
|
||||
GetTableDataByPositionRequest,
|
||||
GetTableDataRequest, // ADD THIS
|
||||
@@ -22,7 +22,7 @@ use common::proto::multieko2::tables_data::{
|
||||
PostTableDataRequest, PostTableDataResponse, PutTableDataRequest,
|
||||
PutTableDataResponse,
|
||||
};
|
||||
use common::proto::multieko2::search::{
|
||||
use common::proto::komp_ac::search::{
|
||||
searcher_client::SearcherClient, SearchRequest, SearchResponse,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
@@ -98,7 +98,7 @@ impl UiService {
|
||||
pub async fn initialize_add_logic_table_data(
|
||||
grpc_client: &mut GrpcClient,
|
||||
add_logic_state: &mut AddLogicState,
|
||||
profile_tree: &common::proto::multieko2::table_definition::ProfileTreeResponse,
|
||||
profile_tree: &common::proto::komp_ac::table_definition::ProfileTreeResponse,
|
||||
) -> Result<String> {
|
||||
let profile_name_clone_opt = Some(add_logic_state.profile_name.clone());
|
||||
let table_name_opt_clone = add_logic_state.selected_table_name.clone();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/state/app/search.rs
|
||||
|
||||
use common::proto::multieko2::search::search_response::Hit;
|
||||
use common::proto::komp_ac::search::search_response::Hit;
|
||||
|
||||
/// Holds the complete state for the search palette.
|
||||
pub struct SearchState {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// src/state/app/state.rs
|
||||
|
||||
use anyhow::Result;
|
||||
use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
||||
use common::proto::komp_ac::table_definition::ProfileTreeResponse;
|
||||
// NEW: Import the types we need for the cache
|
||||
use common::proto::multieko2::table_structure::TableStructureResponse;
|
||||
use common::proto::komp_ac::table_structure::TableStructureResponse;
|
||||
use crate::modes::handlers::mode_manager::AppMode;
|
||||
use crate::state::app::search::SearchState;
|
||||
use crate::ui::handlers::context::DialogPurpose;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/state/pages/canvas_state.rs
|
||||
|
||||
use common::proto::multieko2::search::search_response::Hit;
|
||||
use common::proto::komp_ac::search::search_response::Hit;
|
||||
|
||||
pub trait CanvasState {
|
||||
// --- Existing methods (unchanged) ---
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::state::app::highlight::HighlightState;
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use common::proto::multieko2::search::search_response::Hit;
|
||||
use common::proto::komp_ac::search::search_response::Hit;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::Frame;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::state::pages::add_table::{
|
||||
};
|
||||
use crate::services::GrpcClient;
|
||||
use anyhow::{anyhow, Result};
|
||||
use common::proto::multieko2::table_definition::{
|
||||
use common::proto::komp_ac::table_definition::{
|
||||
PostTableDefinitionRequest,
|
||||
ColumnDefinition as ProtoColumnDefinition,
|
||||
TableLink as ProtoTableLink,
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::state::app::buffer::{AppView, BufferState};
|
||||
use crate::config::storage::storage::{StoredAuthData, save_auth_data};
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crate::ui::handlers::context::DialogPurpose;
|
||||
use common::proto::multieko2::auth::LoginResponse;
|
||||
use common::proto::komp_ac::auth::LoginResponse;
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::state::{
|
||||
};
|
||||
use crate::ui::handlers::context::DialogPurpose;
|
||||
use crate::state::app::buffer::{AppView, BufferState};
|
||||
use common::proto::multieko2::auth::AuthResponse;
|
||||
use common::proto::komp_ac::auth::AuthResponse;
|
||||
use anyhow::Context;
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/utils/data_converter.rs
|
||||
|
||||
use common::proto::multieko2::table_structure::TableStructureResponse;
|
||||
use common::proto::komp_ac::table_structure::TableStructureResponse;
|
||||
use prost_types::{value::Kind, NullValue, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/adresar.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.adresar;
|
||||
package komp_ac.adresar;
|
||||
|
||||
import "common.proto";
|
||||
// import "table_structure.proto";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/auth.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.auth;
|
||||
package komp_ac.auth;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/common.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.common;
|
||||
package komp_ac.common;
|
||||
|
||||
message Empty {}
|
||||
message CountResponse { int64 count = 1; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// In common/proto/search.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.search;
|
||||
package komp_ac.search;
|
||||
|
||||
service Searcher {
|
||||
rpc SearchTable(SearchRequest) returns (SearchResponse);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// common/proto/table_definition.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.table_definition;
|
||||
package komp_ac.table_definition;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service TableDefinition {
|
||||
rpc PostTableDefinition (PostTableDefinitionRequest) returns (TableDefinitionResponse);
|
||||
rpc GetProfileTree (multieko2.common.Empty) returns (ProfileTreeResponse);
|
||||
rpc GetProfileTree (komp_ac.common.Empty) returns (ProfileTreeResponse);
|
||||
rpc DeleteTable (DeleteTableRequest) returns (DeleteTableResponse);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
syntax = "proto3";
|
||||
package multieko2.table_script;
|
||||
package komp_ac.table_script;
|
||||
|
||||
service TableScript {
|
||||
rpc PostTableScript(PostTableScriptRequest) returns (TableScriptResponse);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/table_structure.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.table_structure;
|
||||
package komp_ac.table_structure;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// common/proto/tables_data.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.tables_data;
|
||||
package komp_ac.tables_data;
|
||||
|
||||
import "common.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
@@ -10,7 +10,7 @@ service TablesData {
|
||||
rpc PutTableData (PutTableDataRequest) returns (PutTableDataResponse);
|
||||
rpc DeleteTableData (DeleteTableDataRequest) returns (DeleteTableDataResponse);
|
||||
rpc GetTableData(GetTableDataRequest) returns (GetTableDataResponse);
|
||||
rpc GetTableDataCount(GetTableDataCountRequest) returns (multieko2.common.CountResponse);
|
||||
rpc GetTableDataCount(GetTableDataCountRequest) returns (komp_ac.common.CountResponse);
|
||||
rpc GetTableDataByPosition(GetTableDataByPositionRequest) returns (GetTableDataResponse);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/uctovnictvo.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.uctovnictvo;
|
||||
package komp_ac.uctovnictvo;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
|
||||
@@ -3,33 +3,33 @@
|
||||
pub mod search;
|
||||
|
||||
pub mod proto {
|
||||
pub mod multieko2 {
|
||||
pub mod komp_ac {
|
||||
pub mod adresar {
|
||||
include!("proto/multieko2.adresar.rs");
|
||||
include!("proto/komp_ac.adresar.rs");
|
||||
}
|
||||
pub mod auth {
|
||||
include!("proto/multieko2.auth.rs");
|
||||
include!("proto/komp_ac.auth.rs");
|
||||
}
|
||||
pub mod common {
|
||||
include!("proto/multieko2.common.rs");
|
||||
include!("proto/komp_ac.common.rs");
|
||||
}
|
||||
pub mod table_structure {
|
||||
include!("proto/multieko2.table_structure.rs");
|
||||
include!("proto/komp_ac.table_structure.rs");
|
||||
}
|
||||
pub mod uctovnictvo {
|
||||
include!("proto/multieko2.uctovnictvo.rs");
|
||||
include!("proto/komp_ac.uctovnictvo.rs");
|
||||
}
|
||||
pub mod table_definition {
|
||||
include!("proto/multieko2.table_definition.rs");
|
||||
include!("proto/komp_ac.table_definition.rs");
|
||||
}
|
||||
pub mod tables_data {
|
||||
include!("proto/multieko2.tables_data.rs");
|
||||
include!("proto/komp_ac.tables_data.rs");
|
||||
}
|
||||
pub mod table_script {
|
||||
include!("proto/multieko2.table_script.rs");
|
||||
include!("proto/komp_ac.table_script.rs");
|
||||
}
|
||||
pub mod search {
|
||||
include!("proto/multieko2.search.rs");
|
||||
include!("proto/komp_ac.search.rs");
|
||||
}
|
||||
pub const FILE_DESCRIPTOR_SET: &[u8] =
|
||||
include_bytes!("proto/descriptor.bin");
|
||||
|
||||
Binary file not shown.
791
common/src/proto/komp_ac.adresar.rs
Normal file
791
common/src/proto/komp_ac.adresar.rs
Normal file
@@ -0,0 +1,791 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct GetAdresarRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct DeleteAdresarRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PostAdresarRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub firma: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub kz: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "3")]
|
||||
pub drc: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub ulica: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "5")]
|
||||
pub psc: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "6")]
|
||||
pub mesto: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "7")]
|
||||
pub stat: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "8")]
|
||||
pub banka: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "9")]
|
||||
pub ucet: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "10")]
|
||||
pub skladm: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "11")]
|
||||
pub ico: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "12")]
|
||||
pub kontakt: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "13")]
|
||||
pub telefon: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "14")]
|
||||
pub skladu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "15")]
|
||||
pub fax: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AdresarResponse {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
#[prost(string, tag = "2")]
|
||||
pub firma: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "3")]
|
||||
pub kz: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub drc: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "5")]
|
||||
pub ulica: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "6")]
|
||||
pub psc: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "7")]
|
||||
pub mesto: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "8")]
|
||||
pub stat: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "9")]
|
||||
pub banka: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "10")]
|
||||
pub ucet: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "11")]
|
||||
pub skladm: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "12")]
|
||||
pub ico: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "13")]
|
||||
pub kontakt: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "14")]
|
||||
pub telefon: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "15")]
|
||||
pub skladu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "16")]
|
||||
pub fax: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PutAdresarRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
#[prost(string, tag = "2")]
|
||||
pub firma: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "3")]
|
||||
pub kz: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub drc: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "5")]
|
||||
pub ulica: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "6")]
|
||||
pub psc: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "7")]
|
||||
pub mesto: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "8")]
|
||||
pub stat: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "9")]
|
||||
pub banka: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "10")]
|
||||
pub ucet: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "11")]
|
||||
pub skladm: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "12")]
|
||||
pub ico: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "13")]
|
||||
pub kontakt: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "14")]
|
||||
pub telefon: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "15")]
|
||||
pub skladu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "16")]
|
||||
pub fax: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct DeleteAdresarResponse {
|
||||
#[prost(bool, tag = "1")]
|
||||
pub success: bool,
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod adresar_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AdresarClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl AdresarClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> AdresarClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> AdresarClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
AdresarClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn post_adresar(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PostAdresarRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::AdresarResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.adresar.Adresar/PostAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PostAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_adresar(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::GetAdresarRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::AdresarResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.adresar.Adresar/GetAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn put_adresar(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PutAdresarRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::AdresarResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.adresar.Adresar/PutAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PutAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn delete_adresar(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::DeleteAdresarRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::DeleteAdresarResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.adresar.Adresar/DeleteAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "DeleteAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_adresar_count(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::super::common::Empty>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::super::common::CountResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.adresar.Adresar/GetAdresarCount",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarCount"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_adresar_by_position(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::super::common::PositionRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::AdresarResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.adresar.Adresar/GetAdresarByPosition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarByPosition"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod adresar_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with AdresarServer.
|
||||
#[async_trait]
|
||||
pub trait Adresar: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn post_adresar(
|
||||
&self,
|
||||
request: tonic::Request<super::PostAdresarRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
|
||||
async fn get_adresar(
|
||||
&self,
|
||||
request: tonic::Request<super::GetAdresarRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
|
||||
async fn put_adresar(
|
||||
&self,
|
||||
request: tonic::Request<super::PutAdresarRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
|
||||
async fn delete_adresar(
|
||||
&self,
|
||||
request: tonic::Request<super::DeleteAdresarRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::DeleteAdresarResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_adresar_count(
|
||||
&self,
|
||||
request: tonic::Request<super::super::common::Empty>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::super::common::CountResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_adresar_by_position(
|
||||
&self,
|
||||
request: tonic::Request<super::super::common::PositionRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct AdresarServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> AdresarServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for AdresarServer<T>
|
||||
where
|
||||
T: Adresar,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.adresar.Adresar/PostAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
T: Adresar,
|
||||
> tonic::server::UnaryService<super::PostAdresarRequest>
|
||||
for PostAdresarSvc<T> {
|
||||
type Response = super::AdresarResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PostAdresarRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Adresar>::post_adresar(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PostAdresarSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.adresar.Adresar/GetAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
T: Adresar,
|
||||
> tonic::server::UnaryService<super::GetAdresarRequest>
|
||||
for GetAdresarSvc<T> {
|
||||
type Response = super::AdresarResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::GetAdresarRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Adresar>::get_adresar(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetAdresarSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.adresar.Adresar/PutAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PutAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
T: Adresar,
|
||||
> tonic::server::UnaryService<super::PutAdresarRequest>
|
||||
for PutAdresarSvc<T> {
|
||||
type Response = super::AdresarResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PutAdresarRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Adresar>::put_adresar(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PutAdresarSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.adresar.Adresar/DeleteAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct DeleteAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
T: Adresar,
|
||||
> tonic::server::UnaryService<super::DeleteAdresarRequest>
|
||||
for DeleteAdresarSvc<T> {
|
||||
type Response = super::DeleteAdresarResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::DeleteAdresarRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Adresar>::delete_adresar(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = DeleteAdresarSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.adresar.Adresar/GetAdresarCount" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetAdresarCountSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
T: Adresar,
|
||||
> tonic::server::UnaryService<super::super::common::Empty>
|
||||
for GetAdresarCountSvc<T> {
|
||||
type Response = super::super::common::CountResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::super::common::Empty>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Adresar>::get_adresar_count(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetAdresarCountSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.adresar.Adresar/GetAdresarByPosition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetAdresarByPositionSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
T: Adresar,
|
||||
> tonic::server::UnaryService<super::super::common::PositionRequest>
|
||||
for GetAdresarByPositionSvc<T> {
|
||||
type Response = super::AdresarResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<
|
||||
super::super::common::PositionRequest,
|
||||
>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Adresar>::get_adresar_by_position(&inner, request)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetAdresarByPositionSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for AdresarServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.adresar.Adresar";
|
||||
impl<T> tonic::server::NamedService for AdresarServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
418
common/src/proto/komp_ac.auth.rs
Normal file
418
common/src/proto/komp_ac.auth.rs
Normal file
@@ -0,0 +1,418 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct RegisterRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub username: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub email: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "3")]
|
||||
pub password: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub password_confirmation: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "5")]
|
||||
pub role: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AuthResponse {
|
||||
/// UUID in string format
|
||||
#[prost(string, tag = "1")]
|
||||
pub id: ::prost::alloc::string::String,
|
||||
/// Registered username
|
||||
#[prost(string, tag = "2")]
|
||||
pub username: ::prost::alloc::string::String,
|
||||
/// Registered email (if provided)
|
||||
#[prost(string, tag = "3")]
|
||||
pub email: ::prost::alloc::string::String,
|
||||
/// Default role: 'accountant'
|
||||
#[prost(string, tag = "4")]
|
||||
pub role: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct LoginRequest {
|
||||
/// Can be username or email
|
||||
#[prost(string, tag = "1")]
|
||||
pub identifier: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub password: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct LoginResponse {
|
||||
/// JWT token
|
||||
#[prost(string, tag = "1")]
|
||||
pub access_token: ::prost::alloc::string::String,
|
||||
/// Usually "Bearer"
|
||||
#[prost(string, tag = "2")]
|
||||
pub token_type: ::prost::alloc::string::String,
|
||||
/// Expiration in seconds (86400 for 24 hours)
|
||||
#[prost(int32, tag = "3")]
|
||||
pub expires_in: i32,
|
||||
/// User's UUID in string format
|
||||
#[prost(string, tag = "4")]
|
||||
pub user_id: ::prost::alloc::string::String,
|
||||
/// User's role
|
||||
#[prost(string, tag = "5")]
|
||||
pub role: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "6")]
|
||||
pub username: ::prost::alloc::string::String,
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod auth_service_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthServiceClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl AuthServiceClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> AuthServiceClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> AuthServiceClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
AuthServiceClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn register(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::RegisterRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::AuthResponse>, tonic::Status> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.auth.AuthService/Register",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Register"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn login(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::LoginRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::LoginResponse>, tonic::Status> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.auth.AuthService/Login",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Login"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod auth_service_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with AuthServiceServer.
|
||||
#[async_trait]
|
||||
pub trait AuthService: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn register(
|
||||
&self,
|
||||
request: tonic::Request<super::RegisterRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::AuthResponse>, tonic::Status>;
|
||||
async fn login(
|
||||
&self,
|
||||
request: tonic::Request<super::LoginRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::LoginResponse>, tonic::Status>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct AuthServiceServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> AuthServiceServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for AuthServiceServer<T>
|
||||
where
|
||||
T: AuthService,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.auth.AuthService/Register" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct RegisterSvc<T: AuthService>(pub Arc<T>);
|
||||
impl<
|
||||
T: AuthService,
|
||||
> tonic::server::UnaryService<super::RegisterRequest>
|
||||
for RegisterSvc<T> {
|
||||
type Response = super::AuthResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::RegisterRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as AuthService>::register(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = RegisterSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.auth.AuthService/Login" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct LoginSvc<T: AuthService>(pub Arc<T>);
|
||||
impl<T: AuthService> tonic::server::UnaryService<super::LoginRequest>
|
||||
for LoginSvc<T> {
|
||||
type Response = super::LoginResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::LoginRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as AuthService>::login(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = LoginSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for AuthServiceServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.auth.AuthService";
|
||||
impl<T> tonic::server::NamedService for AuthServiceServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
13
common/src/proto/komp_ac.common.rs
Normal file
13
common/src/proto/komp_ac.common.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct Empty {}
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct CountResponse {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub count: i64,
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct PositionRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub position: i64,
|
||||
}
|
||||
317
common/src/proto/komp_ac.search.rs
Normal file
317
common/src/proto/komp_ac.search.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SearchRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub query: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SearchResponse {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub hits: ::prost::alloc::vec::Vec<search_response::Hit>,
|
||||
}
|
||||
/// Nested message and enum types in `SearchResponse`.
|
||||
pub mod search_response {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Hit {
|
||||
/// PostgreSQL row ID
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
#[prost(float, tag = "2")]
|
||||
pub score: f32,
|
||||
#[prost(string, tag = "3")]
|
||||
pub content_json: ::prost::alloc::string::String,
|
||||
}
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod searcher_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SearcherClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl SearcherClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> SearcherClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> SearcherClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
SearcherClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn search_table(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::SearchRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.search.Searcher/SearchTable",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("komp_ac.search.Searcher", "SearchTable"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod searcher_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with SearcherServer.
|
||||
#[async_trait]
|
||||
pub trait Searcher: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn search_table(
|
||||
&self,
|
||||
request: tonic::Request<super::SearchRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct SearcherServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> SearcherServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for SearcherServer<T>
|
||||
where
|
||||
T: Searcher,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.search.Searcher/SearchTable" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct SearchTableSvc<T: Searcher>(pub Arc<T>);
|
||||
impl<T: Searcher> tonic::server::UnaryService<super::SearchRequest>
|
||||
for SearchTableSvc<T> {
|
||||
type Response = super::SearchResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::SearchRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Searcher>::search_table(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = SearchTableSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for SearcherServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.search.Searcher";
|
||||
impl<T> tonic::server::NamedService for SearcherServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
544
common/src/proto/komp_ac.table_definition.rs
Normal file
544
common/src/proto/komp_ac.table_definition.rs
Normal file
@@ -0,0 +1,544 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TableLink {
|
||||
#[prost(string, tag = "1")]
|
||||
pub linked_table_name: ::prost::alloc::string::String,
|
||||
#[prost(bool, tag = "2")]
|
||||
pub required: bool,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PostTableDefinitionRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub links: ::prost::alloc::vec::Vec<TableLink>,
|
||||
#[prost(message, repeated, tag = "3")]
|
||||
pub columns: ::prost::alloc::vec::Vec<ColumnDefinition>,
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
pub indexes: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, tag = "5")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ColumnDefinition {
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub field_type: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TableDefinitionResponse {
|
||||
#[prost(bool, tag = "1")]
|
||||
pub success: bool,
|
||||
#[prost(string, tag = "2")]
|
||||
pub sql: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProfileTreeResponse {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub profiles: ::prost::alloc::vec::Vec<profile_tree_response::Profile>,
|
||||
}
|
||||
/// Nested message and enum types in `ProfileTreeResponse`.
|
||||
pub mod profile_tree_response {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Table {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
#[prost(string, tag = "2")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
#[prost(string, repeated, tag = "3")]
|
||||
pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Profile {
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub tables: ::prost::alloc::vec::Vec<Table>,
|
||||
}
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DeleteTableRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DeleteTableResponse {
|
||||
#[prost(bool, tag = "1")]
|
||||
pub success: bool,
|
||||
#[prost(string, tag = "2")]
|
||||
pub message: ::prost::alloc::string::String,
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod table_definition_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableDefinitionClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl TableDefinitionClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> TableDefinitionClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> TableDefinitionClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
TableDefinitionClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn post_table_definition(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PostTableDefinitionRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::TableDefinitionResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.table_definition.TableDefinition/PostTableDefinition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"PostTableDefinition",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_profile_tree(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::super::common::Empty>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::ProfileTreeResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.table_definition.TableDefinition/GetProfileTree",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"GetProfileTree",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn delete_table(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::DeleteTableRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::DeleteTableResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.table_definition.TableDefinition/DeleteTable",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"DeleteTable",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod table_definition_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with TableDefinitionServer.
|
||||
#[async_trait]
|
||||
pub trait TableDefinition: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn post_table_definition(
|
||||
&self,
|
||||
request: tonic::Request<super::PostTableDefinitionRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::TableDefinitionResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_profile_tree(
|
||||
&self,
|
||||
request: tonic::Request<super::super::common::Empty>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::ProfileTreeResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn delete_table(
|
||||
&self,
|
||||
request: tonic::Request<super::DeleteTableRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::DeleteTableResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct TableDefinitionServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> TableDefinitionServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for TableDefinitionServer<T>
|
||||
where
|
||||
T: TableDefinition,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.table_definition.TableDefinition/PostTableDefinition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostTableDefinitionSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
T: TableDefinition,
|
||||
> tonic::server::UnaryService<super::PostTableDefinitionRequest>
|
||||
for PostTableDefinitionSvc<T> {
|
||||
type Response = super::TableDefinitionResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PostTableDefinitionRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TableDefinition>::post_table_definition(
|
||||
&inner,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PostTableDefinitionSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.table_definition.TableDefinition/GetProfileTree" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetProfileTreeSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
T: TableDefinition,
|
||||
> tonic::server::UnaryService<super::super::common::Empty>
|
||||
for GetProfileTreeSvc<T> {
|
||||
type Response = super::ProfileTreeResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::super::common::Empty>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TableDefinition>::get_profile_tree(&inner, request)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetProfileTreeSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.table_definition.TableDefinition/DeleteTable" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct DeleteTableSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
T: TableDefinition,
|
||||
> tonic::server::UnaryService<super::DeleteTableRequest>
|
||||
for DeleteTableSvc<T> {
|
||||
type Response = super::DeleteTableResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::DeleteTableRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TableDefinition>::delete_table(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = DeleteTableSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for TableDefinitionServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.table_definition.TableDefinition";
|
||||
impl<T> tonic::server::NamedService for TableDefinitionServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
323
common/src/proto/komp_ac.table_script.rs
Normal file
323
common/src/proto/komp_ac.table_script.rs
Normal file
@@ -0,0 +1,323 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PostTableScriptRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub table_definition_id: i64,
|
||||
#[prost(string, tag = "2")]
|
||||
pub target_column: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "3")]
|
||||
pub script: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub description: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TableScriptResponse {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
#[prost(string, tag = "2")]
|
||||
pub warnings: ::prost::alloc::string::String,
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod table_script_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableScriptClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl TableScriptClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> TableScriptClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> TableScriptClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
TableScriptClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn post_table_script(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PostTableScriptRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::TableScriptResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.table_script.TableScript/PostTableScript",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.table_script.TableScript",
|
||||
"PostTableScript",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod table_script_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with TableScriptServer.
|
||||
#[async_trait]
|
||||
pub trait TableScript: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn post_table_script(
|
||||
&self,
|
||||
request: tonic::Request<super::PostTableScriptRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::TableScriptResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct TableScriptServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> TableScriptServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for TableScriptServer<T>
|
||||
where
|
||||
T: TableScript,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.table_script.TableScript/PostTableScript" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostTableScriptSvc<T: TableScript>(pub Arc<T>);
|
||||
impl<
|
||||
T: TableScript,
|
||||
> tonic::server::UnaryService<super::PostTableScriptRequest>
|
||||
for PostTableScriptSvc<T> {
|
||||
type Response = super::TableScriptResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PostTableScriptRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TableScript>::post_table_script(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PostTableScriptSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for TableScriptServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.table_script.TableScript";
|
||||
impl<T> tonic::server::NamedService for TableScriptServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
336
common/src/proto/komp_ac.table_structure.rs
Normal file
336
common/src/proto/komp_ac.table_structure.rs
Normal file
@@ -0,0 +1,336 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct GetTableStructureRequest {
|
||||
/// e.g., "default"
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
/// e.g., "2025_adresar6"
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TableStructureResponse {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub columns: ::prost::alloc::vec::Vec<TableColumn>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TableColumn {
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
/// e.g., "TEXT", "BIGINT", "VARCHAR(255)", "TIMESTAMPTZ"
|
||||
#[prost(string, tag = "2")]
|
||||
pub data_type: ::prost::alloc::string::String,
|
||||
#[prost(bool, tag = "3")]
|
||||
pub is_nullable: bool,
|
||||
#[prost(bool, tag = "4")]
|
||||
pub is_primary_key: bool,
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod table_structure_service_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableStructureServiceClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl TableStructureServiceClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> TableStructureServiceClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> TableStructureServiceClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
TableStructureServiceClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn get_table_structure(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::GetTableStructureRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::TableStructureResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.table_structure.TableStructureService/GetTableStructure",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.table_structure.TableStructureService",
|
||||
"GetTableStructure",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod table_structure_service_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with TableStructureServiceServer.
|
||||
#[async_trait]
|
||||
pub trait TableStructureService: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn get_table_structure(
|
||||
&self,
|
||||
request: tonic::Request<super::GetTableStructureRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::TableStructureResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct TableStructureServiceServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> TableStructureServiceServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>>
|
||||
for TableStructureServiceServer<T>
|
||||
where
|
||||
T: TableStructureService,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.table_structure.TableStructureService/GetTableStructure" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableStructureSvc<T: TableStructureService>(pub Arc<T>);
|
||||
impl<
|
||||
T: TableStructureService,
|
||||
> tonic::server::UnaryService<super::GetTableStructureRequest>
|
||||
for GetTableStructureSvc<T> {
|
||||
type Response = super::TableStructureResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::GetTableStructureRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TableStructureService>::get_table_structure(
|
||||
&inner,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetTableStructureSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for TableStructureServiceServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.table_structure.TableStructureService";
|
||||
impl<T> tonic::server::NamedService for TableStructureServiceServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
794
common/src/proto/komp_ac.tables_data.rs
Normal file
794
common/src/proto/komp_ac.tables_data.rs
Normal file
@@ -0,0 +1,794 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PostTableDataRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
#[prost(map = "string, message", tag = "3")]
|
||||
pub data: ::std::collections::HashMap<
|
||||
::prost::alloc::string::String,
|
||||
::prost_types::Value,
|
||||
>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PostTableDataResponse {
|
||||
#[prost(bool, tag = "1")]
|
||||
pub success: bool,
|
||||
#[prost(string, tag = "2")]
|
||||
pub message: ::prost::alloc::string::String,
|
||||
#[prost(int64, tag = "3")]
|
||||
pub inserted_id: i64,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PutTableDataRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
#[prost(int64, tag = "3")]
|
||||
pub id: i64,
|
||||
#[prost(map = "string, message", tag = "4")]
|
||||
pub data: ::std::collections::HashMap<
|
||||
::prost::alloc::string::String,
|
||||
::prost_types::Value,
|
||||
>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PutTableDataResponse {
|
||||
#[prost(bool, tag = "1")]
|
||||
pub success: bool,
|
||||
#[prost(string, tag = "2")]
|
||||
pub message: ::prost::alloc::string::String,
|
||||
#[prost(int64, tag = "3")]
|
||||
pub updated_id: i64,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DeleteTableDataRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
#[prost(int64, tag = "3")]
|
||||
pub record_id: i64,
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct DeleteTableDataResponse {
|
||||
#[prost(bool, tag = "1")]
|
||||
pub success: bool,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct GetTableDataRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
#[prost(int64, tag = "3")]
|
||||
pub id: i64,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct GetTableDataResponse {
|
||||
#[prost(map = "string, string", tag = "1")]
|
||||
pub data: ::std::collections::HashMap<
|
||||
::prost::alloc::string::String,
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct GetTableDataCountRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct GetTableDataByPositionRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub table_name: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "3")]
|
||||
pub position: i32,
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod tables_data_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TablesDataClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl TablesDataClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> TablesDataClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> TablesDataClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
TablesDataClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn post_table_data(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PostTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::PostTableDataResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.tables_data.TablesData/PostTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.tables_data.TablesData", "PostTableData"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn put_table_data(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PutTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::PutTableDataResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.tables_data.TablesData/PutTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.tables_data.TablesData", "PutTableData"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn delete_table_data(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::DeleteTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::DeleteTableDataResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.tables_data.TablesData/DeleteTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.tables_data.TablesData", "DeleteTableData"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_table_data(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::GetTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::GetTableDataResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.tables_data.TablesData/GetTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.tables_data.TablesData", "GetTableData"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_table_data_count(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::GetTableDataCountRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::super::common::CountResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataCount",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.tables_data.TablesData",
|
||||
"GetTableDataCount",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_table_data_by_position(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::GetTableDataByPositionRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::GetTableDataResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataByPosition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.tables_data.TablesData",
|
||||
"GetTableDataByPosition",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod tables_data_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with TablesDataServer.
|
||||
#[async_trait]
|
||||
pub trait TablesData: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn post_table_data(
|
||||
&self,
|
||||
request: tonic::Request<super::PostTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::PostTableDataResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn put_table_data(
|
||||
&self,
|
||||
request: tonic::Request<super::PutTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::PutTableDataResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn delete_table_data(
|
||||
&self,
|
||||
request: tonic::Request<super::DeleteTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::DeleteTableDataResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_table_data(
|
||||
&self,
|
||||
request: tonic::Request<super::GetTableDataRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::GetTableDataResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_table_data_count(
|
||||
&self,
|
||||
request: tonic::Request<super::GetTableDataCountRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::super::common::CountResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_table_data_by_position(
|
||||
&self,
|
||||
request: tonic::Request<super::GetTableDataByPositionRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::GetTableDataResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct TablesDataServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> TablesDataServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for TablesDataServer<T>
|
||||
where
|
||||
T: TablesData,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.tables_data.TablesData/PostTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
T: TablesData,
|
||||
> tonic::server::UnaryService<super::PostTableDataRequest>
|
||||
for PostTableDataSvc<T> {
|
||||
type Response = super::PostTableDataResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PostTableDataRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TablesData>::post_table_data(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PostTableDataSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.tables_data.TablesData/PutTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PutTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
T: TablesData,
|
||||
> tonic::server::UnaryService<super::PutTableDataRequest>
|
||||
for PutTableDataSvc<T> {
|
||||
type Response = super::PutTableDataResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PutTableDataRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TablesData>::put_table_data(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PutTableDataSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.tables_data.TablesData/DeleteTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct DeleteTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
T: TablesData,
|
||||
> tonic::server::UnaryService<super::DeleteTableDataRequest>
|
||||
for DeleteTableDataSvc<T> {
|
||||
type Response = super::DeleteTableDataResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::DeleteTableDataRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TablesData>::delete_table_data(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = DeleteTableDataSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.tables_data.TablesData/GetTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
T: TablesData,
|
||||
> tonic::server::UnaryService<super::GetTableDataRequest>
|
||||
for GetTableDataSvc<T> {
|
||||
type Response = super::GetTableDataResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::GetTableDataRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TablesData>::get_table_data(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetTableDataSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataCount" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableDataCountSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
T: TablesData,
|
||||
> tonic::server::UnaryService<super::GetTableDataCountRequest>
|
||||
for GetTableDataCountSvc<T> {
|
||||
type Response = super::super::common::CountResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::GetTableDataCountRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TablesData>::get_table_data_count(&inner, request)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetTableDataCountSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataByPosition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableDataByPositionSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
T: TablesData,
|
||||
> tonic::server::UnaryService<super::GetTableDataByPositionRequest>
|
||||
for GetTableDataByPositionSvc<T> {
|
||||
type Response = super::GetTableDataResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::GetTableDataByPositionRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TablesData>::get_table_data_by_position(
|
||||
&inner,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetTableDataByPositionSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for TablesDataServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.tables_data.TablesData";
|
||||
impl<T> tonic::server::NamedService for TablesDataServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
712
common/src/proto/komp_ac.uctovnictvo.rs
Normal file
712
common/src/proto/komp_ac.uctovnictvo.rs
Normal file
@@ -0,0 +1,712 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PostUctovnictvoRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub adresar_id: i64,
|
||||
#[prost(string, tag = "2")]
|
||||
pub c_dokladu: ::prost::alloc::string::String,
|
||||
/// Use string for simplicity, or use google.protobuf.Timestamp for better date handling
|
||||
#[prost(string, tag = "3")]
|
||||
pub datum: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub c_faktury: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "5")]
|
||||
pub obsah: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "6")]
|
||||
pub stredisko: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "7")]
|
||||
pub c_uctu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "8")]
|
||||
pub md: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "9")]
|
||||
pub identif: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "10")]
|
||||
pub poznanka: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "11")]
|
||||
pub firma: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct UctovnictvoResponse {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
#[prost(int64, tag = "2")]
|
||||
pub adresar_id: i64,
|
||||
#[prost(string, tag = "3")]
|
||||
pub c_dokladu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub datum: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "5")]
|
||||
pub c_faktury: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "6")]
|
||||
pub obsah: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "7")]
|
||||
pub stredisko: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "8")]
|
||||
pub c_uctu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "9")]
|
||||
pub md: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "10")]
|
||||
pub identif: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "11")]
|
||||
pub poznanka: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "12")]
|
||||
pub firma: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PutUctovnictvoRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
#[prost(int64, tag = "2")]
|
||||
pub adresar_id: i64,
|
||||
#[prost(string, tag = "3")]
|
||||
pub c_dokladu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "4")]
|
||||
pub datum: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "5")]
|
||||
pub c_faktury: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "6")]
|
||||
pub obsah: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "7")]
|
||||
pub stredisko: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "8")]
|
||||
pub c_uctu: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "9")]
|
||||
pub md: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "10")]
|
||||
pub identif: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "11")]
|
||||
pub poznanka: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "12")]
|
||||
pub firma: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||
pub struct GetUctovnictvoRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub id: i64,
|
||||
}
|
||||
/// Generated client implementations.
|
||||
pub mod uctovnictvo_client {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
use tonic::codegen::http::Uri;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UctovnictvoClient<T> {
|
||||
inner: tonic::client::Grpc<T>,
|
||||
}
|
||||
impl UctovnictvoClient<tonic::transport::Channel> {
|
||||
/// Attempt to create a new client by connecting to a given endpoint.
|
||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
||||
Ok(Self::new(conn))
|
||||
}
|
||||
}
|
||||
impl<T> UctovnictvoClient<T>
|
||||
where
|
||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
||||
T::Error: Into<StdError>,
|
||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
let inner = tonic::client::Grpc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
||||
Self { inner }
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> UctovnictvoClient<InterceptedService<T, F>>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
T::ResponseBody: Default,
|
||||
T: tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
Response = http::Response<
|
||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
||||
>,
|
||||
>,
|
||||
<T as tonic::codegen::Service<
|
||||
http::Request<tonic::body::Body>,
|
||||
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
|
||||
{
|
||||
UctovnictvoClient::new(InterceptedService::new(inner, interceptor))
|
||||
}
|
||||
/// Compress requests with the given encoding.
|
||||
///
|
||||
/// This requires the server to support it otherwise it might respond with an
|
||||
/// error.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.send_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Enable decompressing responses.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.inner = self.inner.accept_compressed(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_decoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn post_uctovnictvo(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PostUctovnictvoRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.uctovnictvo.Uctovnictvo", "PostUctovnictvo"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_uctovnictvo(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::GetUctovnictvoRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.uctovnictvo.Uctovnictvo", "GetUctovnictvo"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_uctovnictvo_count(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::super::common::Empty>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::super::common::CountResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.uctovnictvo.Uctovnictvo",
|
||||
"GetUctovnictvoCount",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_uctovnictvo_by_position(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::super::common::PositionRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.uctovnictvo.Uctovnictvo",
|
||||
"GetUctovnictvoByPosition",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn put_uctovnictvo(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::PutUctovnictvoRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("komp_ac.uctovnictvo.Uctovnictvo", "PutUctovnictvo"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated server implementations.
|
||||
pub mod uctovnictvo_server {
|
||||
#![allow(
|
||||
unused_variables,
|
||||
dead_code,
|
||||
missing_docs,
|
||||
clippy::wildcard_imports,
|
||||
clippy::let_unit_value,
|
||||
)]
|
||||
use tonic::codegen::*;
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with UctovnictvoServer.
|
||||
#[async_trait]
|
||||
pub trait Uctovnictvo: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn post_uctovnictvo(
|
||||
&self,
|
||||
request: tonic::Request<super::PostUctovnictvoRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_uctovnictvo(
|
||||
&self,
|
||||
request: tonic::Request<super::GetUctovnictvoRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_uctovnictvo_count(
|
||||
&self,
|
||||
request: tonic::Request<super::super::common::Empty>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::super::common::CountResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn get_uctovnictvo_by_position(
|
||||
&self,
|
||||
request: tonic::Request<super::super::common::PositionRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn put_uctovnictvo(
|
||||
&self,
|
||||
request: tonic::Request<super::PutUctovnictvoRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::UctovnictvoResponse>,
|
||||
tonic::Status,
|
||||
>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct UctovnictvoServer<T> {
|
||||
inner: Arc<T>,
|
||||
accept_compression_encodings: EnabledCompressionEncodings,
|
||||
send_compression_encodings: EnabledCompressionEncodings,
|
||||
max_decoding_message_size: Option<usize>,
|
||||
max_encoding_message_size: Option<usize>,
|
||||
}
|
||||
impl<T> UctovnictvoServer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self::from_arc(Arc::new(inner))
|
||||
}
|
||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: Default::default(),
|
||||
send_compression_encodings: Default::default(),
|
||||
max_decoding_message_size: None,
|
||||
max_encoding_message_size: None,
|
||||
}
|
||||
}
|
||||
pub fn with_interceptor<F>(
|
||||
inner: T,
|
||||
interceptor: F,
|
||||
) -> InterceptedService<Self, F>
|
||||
where
|
||||
F: tonic::service::Interceptor,
|
||||
{
|
||||
InterceptedService::new(Self::new(inner), interceptor)
|
||||
}
|
||||
/// Enable decompressing requests with the given encoding.
|
||||
#[must_use]
|
||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.accept_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Compress responses with the given encoding, if the client supports it.
|
||||
#[must_use]
|
||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
||||
self.send_compression_encodings.enable(encoding);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of a decoded message.
|
||||
///
|
||||
/// Default: `4MB`
|
||||
#[must_use]
|
||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_decoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
/// Limits the maximum size of an encoded message.
|
||||
///
|
||||
/// Default: `usize::MAX`
|
||||
#[must_use]
|
||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
||||
self.max_encoding_message_size = Some(limit);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for UctovnictvoServer<T>
|
||||
where
|
||||
T: Uctovnictvo,
|
||||
B: Body + std::marker::Send + 'static,
|
||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
||||
{
|
||||
type Response = http::Response<tonic::body::Body>;
|
||||
type Error = std::convert::Infallible;
|
||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
T: Uctovnictvo,
|
||||
> tonic::server::UnaryService<super::PostUctovnictvoRequest>
|
||||
for PostUctovnictvoSvc<T> {
|
||||
type Response = super::UctovnictvoResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PostUctovnictvoRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Uctovnictvo>::post_uctovnictvo(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PostUctovnictvoSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
T: Uctovnictvo,
|
||||
> tonic::server::UnaryService<super::GetUctovnictvoRequest>
|
||||
for GetUctovnictvoSvc<T> {
|
||||
type Response = super::UctovnictvoResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::GetUctovnictvoRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Uctovnictvo>::get_uctovnictvo(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetUctovnictvoSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetUctovnictvoCountSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
T: Uctovnictvo,
|
||||
> tonic::server::UnaryService<super::super::common::Empty>
|
||||
for GetUctovnictvoCountSvc<T> {
|
||||
type Response = super::super::common::CountResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::super::common::Empty>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Uctovnictvo>::get_uctovnictvo_count(&inner, request)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetUctovnictvoCountSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetUctovnictvoByPositionSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
T: Uctovnictvo,
|
||||
> tonic::server::UnaryService<super::super::common::PositionRequest>
|
||||
for GetUctovnictvoByPositionSvc<T> {
|
||||
type Response = super::UctovnictvoResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<
|
||||
super::super::common::PositionRequest,
|
||||
>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Uctovnictvo>::get_uctovnictvo_by_position(
|
||||
&inner,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetUctovnictvoByPositionSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PutUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
T: Uctovnictvo,
|
||||
> tonic::server::UnaryService<super::PutUctovnictvoRequest>
|
||||
for PutUctovnictvoSvc<T> {
|
||||
type Response = super::UctovnictvoResponse;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::PutUctovnictvoRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as Uctovnictvo>::put_uctovnictvo(&inner, request).await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = PutUctovnictvoSvc(inner);
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
_ => {
|
||||
Box::pin(async move {
|
||||
let mut response = http::Response::new(
|
||||
tonic::body::Body::default(),
|
||||
);
|
||||
let headers = response.headers_mut();
|
||||
headers
|
||||
.insert(
|
||||
tonic::Status::GRPC_STATUS,
|
||||
(tonic::Code::Unimplemented as i32).into(),
|
||||
);
|
||||
headers
|
||||
.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
||||
);
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for UctovnictvoServer<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let inner = self.inner.clone();
|
||||
Self {
|
||||
inner,
|
||||
accept_compression_encodings: self.accept_compression_encodings,
|
||||
send_compression_encodings: self.send_compression_encodings,
|
||||
max_decoding_message_size: self.max_decoding_message_size,
|
||||
max_encoding_message_size: self.max_encoding_message_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "komp_ac.uctovnictvo.Uctovnictvo";
|
||||
impl<T> tonic::server::NamedService for UctovnictvoServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
}
|
||||
@@ -225,11 +225,11 @@ pub mod adresar_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.adresar.Adresar/PostAdresar",
|
||||
"/komp_ac.adresar.Adresar/PostAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "PostAdresar"));
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PostAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_adresar(
|
||||
@@ -249,11 +249,11 @@ pub mod adresar_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.adresar.Adresar/GetAdresar",
|
||||
"/komp_ac.adresar.Adresar/GetAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "GetAdresar"));
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn put_adresar(
|
||||
@@ -273,11 +273,11 @@ pub mod adresar_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.adresar.Adresar/PutAdresar",
|
||||
"/komp_ac.adresar.Adresar/PutAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "PutAdresar"));
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PutAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn delete_adresar(
|
||||
@@ -297,11 +297,11 @@ pub mod adresar_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.adresar.Adresar/DeleteAdresar",
|
||||
"/komp_ac.adresar.Adresar/DeleteAdresar",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "DeleteAdresar"));
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "DeleteAdresar"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_adresar_count(
|
||||
@@ -321,11 +321,11 @@ pub mod adresar_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.adresar.Adresar/GetAdresarCount",
|
||||
"/komp_ac.adresar.Adresar/GetAdresarCount",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "GetAdresarCount"));
|
||||
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarCount"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn get_adresar_by_position(
|
||||
@@ -345,12 +345,12 @@ pub mod adresar_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.adresar.Adresar/GetAdresarByPosition",
|
||||
"/komp_ac.adresar.Adresar/GetAdresarByPosition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("multieko2.adresar.Adresar", "GetAdresarByPosition"),
|
||||
GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarByPosition"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
@@ -476,7 +476,7 @@ pub mod adresar_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.adresar.Adresar/PostAdresar" => {
|
||||
"/komp_ac.adresar.Adresar/PostAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -521,7 +521,7 @@ pub mod adresar_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.adresar.Adresar/GetAdresar" => {
|
||||
"/komp_ac.adresar.Adresar/GetAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -566,7 +566,7 @@ pub mod adresar_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.adresar.Adresar/PutAdresar" => {
|
||||
"/komp_ac.adresar.Adresar/PutAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PutAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -611,7 +611,7 @@ pub mod adresar_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.adresar.Adresar/DeleteAdresar" => {
|
||||
"/komp_ac.adresar.Adresar/DeleteAdresar" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct DeleteAdresarSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -656,7 +656,7 @@ pub mod adresar_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.adresar.Adresar/GetAdresarCount" => {
|
||||
"/komp_ac.adresar.Adresar/GetAdresarCount" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetAdresarCountSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -701,7 +701,7 @@ pub mod adresar_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.adresar.Adresar/GetAdresarByPosition" => {
|
||||
"/komp_ac.adresar.Adresar/GetAdresarByPosition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetAdresarByPositionSvc<T: Adresar>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -784,7 +784,7 @@ pub mod adresar_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.adresar.Adresar";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.adresar.Adresar";
|
||||
impl<T> tonic::server::NamedService for AdresarServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -160,11 +160,11 @@ pub mod auth_service_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.auth.AuthService/Register",
|
||||
"/komp_ac.auth.AuthService/Register",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.auth.AuthService", "Register"));
|
||||
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Register"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn login(
|
||||
@@ -181,11 +181,11 @@ pub mod auth_service_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.auth.AuthService/Login",
|
||||
"/komp_ac.auth.AuthService/Login",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.auth.AuthService", "Login"));
|
||||
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Login"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
@@ -288,7 +288,7 @@ pub mod auth_service_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.auth.AuthService/Register" => {
|
||||
"/komp_ac.auth.AuthService/Register" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct RegisterSvc<T: AuthService>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -333,7 +333,7 @@ pub mod auth_service_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.auth.AuthService/Login" => {
|
||||
"/komp_ac.auth.AuthService/Login" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct LoginSvc<T: AuthService>(pub Arc<T>);
|
||||
impl<T: AuthService> tonic::server::UnaryService<super::LoginRequest>
|
||||
@@ -411,7 +411,7 @@ pub mod auth_service_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.auth.AuthService";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.auth.AuthService";
|
||||
impl<T> tonic::server::NamedService for AuthServiceServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -129,11 +129,11 @@ pub mod searcher_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.search.Searcher/SearchTable",
|
||||
"/komp_ac.search.Searcher/SearchTable",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(GrpcMethod::new("multieko2.search.Searcher", "SearchTable"));
|
||||
.insert(GrpcMethod::new("komp_ac.search.Searcher", "SearchTable"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ pub mod searcher_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.search.Searcher/SearchTable" => {
|
||||
"/komp_ac.search.Searcher/SearchTable" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct SearchTableSvc<T: Searcher>(pub Arc<T>);
|
||||
impl<T: Searcher> tonic::server::UnaryService<super::SearchRequest>
|
||||
@@ -310,7 +310,7 @@ pub mod searcher_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.search.Searcher";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.search.Searcher";
|
||||
impl<T> tonic::server::NamedService for SearcherServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -179,13 +179,13 @@ pub mod table_definition_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.table_definition.TableDefinition/PostTableDefinition",
|
||||
"/komp_ac.table_definition.TableDefinition/PostTableDefinition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.table_definition.TableDefinition",
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"PostTableDefinition",
|
||||
),
|
||||
);
|
||||
@@ -208,13 +208,13 @@ pub mod table_definition_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.table_definition.TableDefinition/GetProfileTree",
|
||||
"/komp_ac.table_definition.TableDefinition/GetProfileTree",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.table_definition.TableDefinition",
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"GetProfileTree",
|
||||
),
|
||||
);
|
||||
@@ -237,13 +237,13 @@ pub mod table_definition_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.table_definition.TableDefinition/DeleteTable",
|
||||
"/komp_ac.table_definition.TableDefinition/DeleteTable",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.table_definition.TableDefinition",
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"DeleteTable",
|
||||
),
|
||||
);
|
||||
@@ -362,7 +362,7 @@ pub mod table_definition_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.table_definition.TableDefinition/PostTableDefinition" => {
|
||||
"/komp_ac.table_definition.TableDefinition/PostTableDefinition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostTableDefinitionSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -411,7 +411,7 @@ pub mod table_definition_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.table_definition.TableDefinition/GetProfileTree" => {
|
||||
"/komp_ac.table_definition.TableDefinition/GetProfileTree" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetProfileTreeSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -457,7 +457,7 @@ pub mod table_definition_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.table_definition.TableDefinition/DeleteTable" => {
|
||||
"/komp_ac.table_definition.TableDefinition/DeleteTable" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct DeleteTableSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -537,7 +537,7 @@ pub mod table_definition_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.table_definition.TableDefinition";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.table_definition.TableDefinition";
|
||||
impl<T> tonic::server::NamedService for TableDefinitionServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -125,13 +125,13 @@ pub mod table_script_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.table_script.TableScript/PostTableScript",
|
||||
"/komp_ac.table_script.TableScript/PostTableScript",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.table_script.TableScript",
|
||||
"komp_ac.table_script.TableScript",
|
||||
"PostTableScript",
|
||||
),
|
||||
);
|
||||
@@ -236,7 +236,7 @@ pub mod table_script_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.table_script.TableScript/PostTableScript" => {
|
||||
"/komp_ac.table_script.TableScript/PostTableScript" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostTableScriptSvc<T: TableScript>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -316,7 +316,7 @@ pub mod table_script_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.table_script.TableScript";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.table_script.TableScript";
|
||||
impl<T> tonic::server::NamedService for TableScriptServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -133,13 +133,13 @@ pub mod table_structure_service_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.table_structure.TableStructureService/GetTableStructure",
|
||||
"/komp_ac.table_structure.TableStructureService/GetTableStructure",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.table_structure.TableStructureService",
|
||||
"komp_ac.table_structure.TableStructureService",
|
||||
"GetTableStructure",
|
||||
),
|
||||
);
|
||||
@@ -245,7 +245,7 @@ pub mod table_structure_service_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.table_structure.TableStructureService/GetTableStructure" => {
|
||||
"/komp_ac.table_structure.TableStructureService/GetTableStructure" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableStructureSvc<T: TableStructureService>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -329,7 +329,7 @@ pub mod table_structure_service_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.table_structure.TableStructureService";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.table_structure.TableStructureService";
|
||||
impl<T> tonic::server::NamedService for TableStructureServiceServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -198,12 +198,12 @@ pub mod tables_data_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.tables_data.TablesData/PostTableData",
|
||||
"/komp_ac.tables_data.TablesData/PostTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("multieko2.tables_data.TablesData", "PostTableData"),
|
||||
GrpcMethod::new("komp_ac.tables_data.TablesData", "PostTableData"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
@@ -224,12 +224,12 @@ pub mod tables_data_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.tables_data.TablesData/PutTableData",
|
||||
"/komp_ac.tables_data.TablesData/PutTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("multieko2.tables_data.TablesData", "PutTableData"),
|
||||
GrpcMethod::new("komp_ac.tables_data.TablesData", "PutTableData"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
@@ -250,13 +250,13 @@ pub mod tables_data_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.tables_data.TablesData/DeleteTableData",
|
||||
"/komp_ac.tables_data.TablesData/DeleteTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.tables_data.TablesData",
|
||||
"komp_ac.tables_data.TablesData",
|
||||
"DeleteTableData",
|
||||
),
|
||||
);
|
||||
@@ -279,12 +279,12 @@ pub mod tables_data_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.tables_data.TablesData/GetTableData",
|
||||
"/komp_ac.tables_data.TablesData/GetTableData",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new("multieko2.tables_data.TablesData", "GetTableData"),
|
||||
GrpcMethod::new("komp_ac.tables_data.TablesData", "GetTableData"),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
@@ -305,13 +305,13 @@ pub mod tables_data_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.tables_data.TablesData/GetTableDataCount",
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataCount",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.tables_data.TablesData",
|
||||
"komp_ac.tables_data.TablesData",
|
||||
"GetTableDataCount",
|
||||
),
|
||||
);
|
||||
@@ -334,13 +334,13 @@ pub mod tables_data_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.tables_data.TablesData/GetTableDataByPosition",
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataByPosition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.tables_data.TablesData",
|
||||
"komp_ac.tables_data.TablesData",
|
||||
"GetTableDataByPosition",
|
||||
),
|
||||
);
|
||||
@@ -480,7 +480,7 @@ pub mod tables_data_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.tables_data.TablesData/PostTableData" => {
|
||||
"/komp_ac.tables_data.TablesData/PostTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -525,7 +525,7 @@ pub mod tables_data_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.tables_data.TablesData/PutTableData" => {
|
||||
"/komp_ac.tables_data.TablesData/PutTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PutTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -570,7 +570,7 @@ pub mod tables_data_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.tables_data.TablesData/DeleteTableData" => {
|
||||
"/komp_ac.tables_data.TablesData/DeleteTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct DeleteTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -615,7 +615,7 @@ pub mod tables_data_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.tables_data.TablesData/GetTableData" => {
|
||||
"/komp_ac.tables_data.TablesData/GetTableData" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -660,7 +660,7 @@ pub mod tables_data_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.tables_data.TablesData/GetTableDataCount" => {
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataCount" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableDataCountSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -706,7 +706,7 @@ pub mod tables_data_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.tables_data.TablesData/GetTableDataByPosition" => {
|
||||
"/komp_ac.tables_data.TablesData/GetTableDataByPosition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetTableDataByPositionSvc<T: TablesData>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -790,7 +790,7 @@ pub mod tables_data_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.tables_data.TablesData";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.tables_data.TablesData";
|
||||
impl<T> tonic::server::NamedService for TablesDataServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -192,13 +192,13 @@ pub mod uctovnictvo_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/PostUctovnictvo",
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.uctovnictvo.Uctovnictvo",
|
||||
"komp_ac.uctovnictvo.Uctovnictvo",
|
||||
"PostUctovnictvo",
|
||||
),
|
||||
);
|
||||
@@ -221,13 +221,13 @@ pub mod uctovnictvo_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvo",
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.uctovnictvo.Uctovnictvo",
|
||||
"komp_ac.uctovnictvo.Uctovnictvo",
|
||||
"GetUctovnictvo",
|
||||
),
|
||||
);
|
||||
@@ -250,13 +250,13 @@ pub mod uctovnictvo_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoCount",
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.uctovnictvo.Uctovnictvo",
|
||||
"komp_ac.uctovnictvo.Uctovnictvo",
|
||||
"GetUctovnictvoCount",
|
||||
),
|
||||
);
|
||||
@@ -279,13 +279,13 @@ pub mod uctovnictvo_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition",
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.uctovnictvo.Uctovnictvo",
|
||||
"komp_ac.uctovnictvo.Uctovnictvo",
|
||||
"GetUctovnictvoByPosition",
|
||||
),
|
||||
);
|
||||
@@ -308,13 +308,13 @@ pub mod uctovnictvo_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/PutUctovnictvo",
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"multieko2.uctovnictvo.Uctovnictvo",
|
||||
"komp_ac.uctovnictvo.Uctovnictvo",
|
||||
"PutUctovnictvo",
|
||||
),
|
||||
);
|
||||
@@ -447,7 +447,7 @@ pub mod uctovnictvo_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/PostUctovnictvo" => {
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -492,7 +492,7 @@ pub mod uctovnictvo_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvo" => {
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -537,7 +537,7 @@ pub mod uctovnictvo_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoCount" => {
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetUctovnictvoCountSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -583,7 +583,7 @@ pub mod uctovnictvo_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition" => {
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetUctovnictvoByPositionSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -634,7 +634,7 @@ pub mod uctovnictvo_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/multieko2.uctovnictvo.Uctovnictvo/PutUctovnictvo" => {
|
||||
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PutUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
|
||||
impl<
|
||||
@@ -714,7 +714,7 @@ pub mod uctovnictvo_server {
|
||||
}
|
||||
}
|
||||
/// Generated gRPC service name
|
||||
pub const SERVICE_NAME: &str = "multieko2.uctovnictvo.Uctovnictvo";
|
||||
pub const SERVICE_NAME: &str = "komp_ac.uctovnictvo.Uctovnictvo";
|
||||
impl<T> tonic::server::NamedService for UctovnictvoServer<T> {
|
||||
const NAME: &'static str = SERVICE_NAME;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ feature-depth = 1
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
{ id = "RUSTSEC-2024-0014", reason = "generational-arena is archived but no safe upgrade path exists; accepted for now" },
|
||||
{ id = "RUSTSEC-2024-0436", reason = "paste is archived; no immediate alternative in dependency tree; accepted for now" },
|
||||
#"RUSTSEC-0000-0000",
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
@@ -100,6 +102,7 @@ allow = [
|
||||
"HPND",
|
||||
"ISC",
|
||||
"LGPL-3.0",
|
||||
"AGPL-3.0",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"NCSA",
|
||||
|
||||
@@ -11,11 +11,11 @@ use tantivy::schema::{IndexRecordOption, Value};
|
||||
use tantivy::{Index, TantivyDocument, Term};
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use common::proto::multieko2::search::{
|
||||
use common::proto::komp_ac::search::{
|
||||
search_response::Hit, SearchRequest, SearchResponse,
|
||||
};
|
||||
pub use common::proto::multieko2::search::searcher_server::SearcherServer;
|
||||
use common::proto::multieko2::search::searcher_server::Searcher;
|
||||
pub use common::proto::komp_ac::search::searcher_server::SearcherServer;
|
||||
use common::proto::komp_ac::search::searcher_server::Searcher;
|
||||
use common::search::register_slovak_tokenizers;
|
||||
use sqlx::{PgPool, Row};
|
||||
use tracing::info;
|
||||
|
||||
@@ -22,19 +22,23 @@ tonic = "0.13.0"
|
||||
tonic-reflection = "0.13.0"
|
||||
tracing = "0.1.41"
|
||||
time = { version = "0.3.41", features = ["local-offset"] }
|
||||
steel-derive = { git = "https://github.com/mattwparas/steel.git", branch = "master", package = "steel-derive" }
|
||||
steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.6.0", features = ["anyhow", "dylibs", "sync"] }
|
||||
thiserror = "2.0.12"
|
||||
|
||||
steel-derive = "0.6.0"
|
||||
steel-core = { version = "0.7.0", features = ["anyhow", "dylibs", "sync"] }
|
||||
|
||||
dashmap = "6.1.0"
|
||||
lazy_static = "1.5.0"
|
||||
regex = "1.11.1"
|
||||
bcrypt = "0.17.0"
|
||||
validator = { version = "0.20.0", features = ["derive"] }
|
||||
uuid = { version = "1.16.0", features = ["serde", "v4"] }
|
||||
jsonwebtoken = "9.3.1"
|
||||
rust-stemmers = "1.2.0"
|
||||
rust_decimal = { version = "1.37.2", features = ["maths", "serde"] }
|
||||
rust_decimal_macros = "1.37.1"
|
||||
|
||||
rust_decimal = { workspace = true }
|
||||
rust_decimal_macros = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
steel-decimal = "1.0.0"
|
||||
|
||||
[lib]
|
||||
name = "server"
|
||||
@@ -46,3 +50,4 @@ rstest = "0.25.0"
|
||||
lazy_static = "1.5.0"
|
||||
rand = "0.9.1"
|
||||
futures = "0.3.31"
|
||||
tokio-test = "0.4.4"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
-- migrations/20250710201933_create_script_dependencies.sql
|
||||
|
||||
-- This table stores the dependency graph for table scripts
|
||||
-- More efficient design with better indexing
|
||||
CREATE TABLE script_dependencies (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
|
||||
-- The script that creates this dependency
|
||||
script_id BIGINT NOT NULL REFERENCES table_scripts(id) ON DELETE CASCADE,
|
||||
|
||||
-- The table that depends on another (source of dependency)
|
||||
source_table_id BIGINT NOT NULL REFERENCES table_definitions(id) ON DELETE CASCADE,
|
||||
|
||||
-- The table being depended upon (target of dependency)
|
||||
target_table_id BIGINT NOT NULL REFERENCES table_definitions(id) ON DELETE CASCADE,
|
||||
|
||||
-- What type of dependency (for better debugging)
|
||||
dependency_type TEXT NOT NULL CHECK (dependency_type IN ('column_access', 'sql_query', 'indexed_access')),
|
||||
|
||||
-- Additional context (column name, query snippet, etc.)
|
||||
context_info JSONB,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Optimized indexes for fast cycle detection
|
||||
CREATE INDEX idx_script_deps_source_target ON script_dependencies (source_table_id, target_table_id);
|
||||
CREATE INDEX idx_script_deps_by_script ON script_dependencies (script_id);
|
||||
CREATE INDEX idx_script_deps_by_target ON script_dependencies (target_table_id);
|
||||
|
||||
-- View for easy dependency analysis
|
||||
CREATE VIEW dependency_graph AS
|
||||
SELECT
|
||||
sd.source_table_id,
|
||||
sd.target_table_id,
|
||||
st.table_name AS source_table,
|
||||
tt.table_name AS target_table,
|
||||
sd.dependency_type,
|
||||
sd.context_info,
|
||||
s.name AS schema_name
|
||||
FROM script_dependencies sd
|
||||
JOIN table_definitions st ON sd.source_table_id = st.id
|
||||
JOIN table_definitions tt ON sd.target_table_id = tt.id
|
||||
JOIN schemas s ON st.schema_id = s.id;
|
||||
@@ -1,7 +1,7 @@
|
||||
❯ grpcurl -plaintext -d '{
|
||||
"username": "testuser2",
|
||||
"email": "test2@example.com"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Register
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Register
|
||||
{
|
||||
"id": "5fa9bbce-85e0-4b06-8364-b561770c2fdd",
|
||||
"username": "testuser2",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"email": "test3@example.com",
|
||||
"password": "your_password",
|
||||
"password_confirmation": "your_password"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Register
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Register
|
||||
{
|
||||
"id": "96d2fd35-b39d-4c05-916a-66134453d34c",
|
||||
"username": "testuser3",
|
||||
@@ -12,14 +12,14 @@
|
||||
}
|
||||
❯ grpcurl -plaintext -d '{
|
||||
"identifier": "testuser3"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Login
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Login
|
||||
ERROR:
|
||||
Code: Unauthenticated
|
||||
Message: Invalid credentials
|
||||
❯ grpcurl -plaintext -d '{
|
||||
"identifier": "testuser3",
|
||||
"password": "your_password"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Login
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Login
|
||||
{
|
||||
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI5NmQyZmQzNS1iMzlkLTRjMDUtOTE2YS02NjEzNDQ1M2QzNGMiLCJleHAiOjE3NDI5ODE2MTAsInJvbGUiOiJhY2NvdW50YW50In0.78VIR3X4QZohzeI5x3xmkmqcICTusOC6PELPohMV-k8",
|
||||
"tokenType": "Bearer",
|
||||
@@ -30,7 +30,7 @@ ERROR:
|
||||
❯ grpcurl -plaintext -d '{
|
||||
"username": "testuser4",
|
||||
"email": "test4@example.com"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Register
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Register
|
||||
{
|
||||
"id": "413d7ecc-f231-48af-8c5a-566b1dc2bf0b",
|
||||
"username": "testuser4",
|
||||
@@ -39,7 +39,7 @@ ERROR:
|
||||
}
|
||||
❯ grpcurl -plaintext -d '{
|
||||
"identifier": "test4@example.com"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Login
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Login
|
||||
{
|
||||
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MTNkN2VjYy1mMjMxLTQ4YWYtOGM1YS01NjZiMWRjMmJmMGIiLCJleHAiOjE3NDI5ODE3MDEsInJvbGUiOiJhY2NvdW50YW50In0.4Hzu3tTZRNGHnBSgeCbGy2tFTl8EzpPdXBhcW8kuIc8",
|
||||
"tokenType": "Bearer",
|
||||
@@ -47,5 +47,5 @@ ERROR:
|
||||
"userId": "413d7ecc-f231-48af-8c5a-566b1dc2bf0b",
|
||||
"role": "accountant"
|
||||
}
|
||||
╭─ ~/Doc/pr/multieko2/server auth ······ ✔
|
||||
╭─ ~/Doc/pr/komp_ac/server auth ······ ✔
|
||||
╰─
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"email": "default@example.com",
|
||||
"password": "password",
|
||||
"password_confirmation": "password"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Register
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Register
|
||||
{
|
||||
"id": "73017288-af41-49d8-b4cb-2ac2109b38a1",
|
||||
"username": "testuser_default_role",
|
||||
@@ -16,7 +16,7 @@
|
||||
"password": "password",
|
||||
"password_confirmation": "password",
|
||||
"role": "admin"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Register
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Register
|
||||
{
|
||||
"id": "9437e318-818b-4c34-822d-c2d6601b835e",
|
||||
"username": "testuser_admin_role",
|
||||
@@ -29,9 +29,9 @@
|
||||
"password": "password",
|
||||
"password_confirmation": "password",
|
||||
"role": "invalid_role_name"
|
||||
}' localhost:50051 multieko2.auth.AuthService/Register
|
||||
}' localhost:50051 komp_ac.auth.AuthService/Register
|
||||
ERROR:
|
||||
Code: InvalidArgument
|
||||
Message: Invalid role specified: 'invalid_role_name'
|
||||
╭─ ~/Doc/pr/multieko2/server main +3 !1
|
||||
╭─ ~/Doc/pr/komp_ac/server main +3 !1
|
||||
╰─
|
||||
|
||||
@@ -3,7 +3,7 @@ use bcrypt::verify;
|
||||
use tonic::{Response, Status};
|
||||
use crate::db::PgPool;
|
||||
use crate::auth::logic::jwt; // Fixed import path
|
||||
use common::proto::multieko2::auth::{LoginRequest, LoginResponse};
|
||||
use common::proto::komp_ac::auth::{LoginRequest, LoginResponse};
|
||||
|
||||
pub async fn login(
|
||||
pool: &PgPool,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/auth/handlers/register.rs
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use tonic::{Response, Status};
|
||||
use common::proto::multieko2::auth::{RegisterRequest, AuthResponse};
|
||||
use common::proto::komp_ac::auth::{RegisterRequest, AuthResponse};
|
||||
use crate::db::PgPool;
|
||||
use crate::auth::models::AuthError;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use tonic_reflection::server::Builder as ReflectionBuilder;
|
||||
use tokio::sync::mpsc;
|
||||
use crate::indexer::{indexer_task, IndexCommand};
|
||||
|
||||
use common::proto::multieko2::FILE_DESCRIPTOR_SET;
|
||||
use common::proto::komp_ac::FILE_DESCRIPTOR_SET;
|
||||
use crate::server::services::{
|
||||
TableStructureHandler,
|
||||
TableDefinitionService,
|
||||
@@ -12,7 +12,7 @@ use crate::server::services::{
|
||||
TableScriptService,
|
||||
AuthServiceImpl
|
||||
};
|
||||
use common::proto::multieko2::{
|
||||
use common::proto::komp_ac::{
|
||||
table_structure::table_structure_service_server::TableStructureServiceServer,
|
||||
table_definition::table_definition_server::TableDefinitionServer,
|
||||
tables_data::tables_data_server::TablesDataServer,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/server/services/auth_service.rs
|
||||
use tonic::{Request, Response, Status};
|
||||
use common::proto::multieko2::auth::{
|
||||
use common::proto::komp_ac::auth::{
|
||||
auth_service_server::AuthService,
|
||||
RegisterRequest, AuthResponse,
|
||||
LoginRequest, LoginResponse
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/server/services/table_definition_service.rs
|
||||
use tonic::{Request, Response, Status};
|
||||
use common::proto::multieko2::{
|
||||
use common::proto::komp_ac::{
|
||||
common::Empty,
|
||||
table_definition::{
|
||||
table_definition_server::TableDefinition,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/server/services/table_script_service.rs
|
||||
use tonic::{Request, Response, Status};
|
||||
use common::proto::multieko2::table_script::{
|
||||
use common::proto::komp_ac::table_script::{
|
||||
table_script_server::TableScript,
|
||||
PostTableScriptRequest, TableScriptResponse
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// src/server/services/table_structure_service.rs
|
||||
use tonic::{Request, Response, Status};
|
||||
// Correct the import path for the TableStructureService trait
|
||||
use common::proto::multieko2::table_structure::table_structure_service_server::TableStructureService;
|
||||
use common::proto::multieko2::table_structure::{
|
||||
use common::proto::komp_ac::table_structure::table_structure_service_server::TableStructureService;
|
||||
use common::proto::komp_ac::table_structure::{
|
||||
GetTableStructureRequest,
|
||||
TableStructureResponse,
|
||||
};
|
||||
|
||||
@@ -5,9 +5,9 @@ use tonic::{Request, Response, Status};
|
||||
use tokio::sync::mpsc;
|
||||
use crate::indexer::IndexCommand;
|
||||
|
||||
use common::proto::multieko2::tables_data::tables_data_server::TablesData;
|
||||
use common::proto::multieko2::common::CountResponse;
|
||||
use common::proto::multieko2::tables_data::{
|
||||
use common::proto::komp_ac::tables_data::tables_data_server::TablesData;
|
||||
use common::proto::komp_ac::common::CountResponse;
|
||||
use common::proto::komp_ac::tables_data::{
|
||||
PostTableDataRequest, PostTableDataResponse,
|
||||
PutTableDataRequest, PutTableDataResponse,
|
||||
DeleteTableDataRequest, DeleteTableDataResponse,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"target_column": "idea_name",
|
||||
"script": "(begin (define idea_name \"aaa\") idea_name)",
|
||||
"description": "Set idea_name to Awesome Idea"
|
||||
}' localhost:50051 multieko2.table_script.TableScript/PostTableScript
|
||||
}' localhost:50051 komp_ac.table_script.TableScript/PostTableScript
|
||||
{
|
||||
"id": "7"
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
"is_feasible": "true",
|
||||
"last_updated": "2025-03-11T12:34:56Z"
|
||||
}
|
||||
}' localhost:50051 multieko2.tables_data.TablesData/PostTableData
|
||||
}' localhost:50051 komp_ac.tables_data.TablesData/PostTableData
|
||||
ERROR:
|
||||
Code: InvalidArgument
|
||||
Message: Validation failed for 'idea_name'. Expected: 'aaa', Received: 'sdfsfs'
|
||||
@@ -31,7 +31,7 @@ ERROR:
|
||||
"is_feasible": "true",
|
||||
"last_updated": "2025-03-11T12:34:56Z"
|
||||
}
|
||||
}' localhost:50051 multieko2.tables_data.TablesData/PostTableData
|
||||
}' localhost:50051 komp_ac.tables_data.TablesData/PostTableData
|
||||
{
|
||||
"success": true,
|
||||
"message": "Data inserted successfully",
|
||||
|
||||
@@ -6,7 +6,7 @@ Creation of the tables:
|
||||
{"name": "yearly_goal", "field_type": "text"}
|
||||
],
|
||||
"profile_name": "finance"
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
|
||||
{
|
||||
"success": true,
|
||||
"sql": "CREATE TABLE \"2025_department\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n \"yearly_goal\" TEXT,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\n"
|
||||
@@ -22,7 +22,7 @@ Creation of the tables:
|
||||
"links": [
|
||||
{"linked_table_name": "2025_department", "required": true}
|
||||
]
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
|
||||
{
|
||||
"success": true,
|
||||
"sql": "CREATE TABLE \"2025_project\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n \"department_id\" BIGINT NOT NULL REFERENCES \"2025_department\"(id),\n \"name\" TEXT,\n \"budget_estimate\" TEXT,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_project_department_fk ON \"2025_project\" (\"department_id\")\nCREATE INDEX idx_2025_project_name ON \"2025_project\" (\"name\")"
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
// src/steel/server/decimal_math.rs
|
||||
use rust_decimal::prelude::*;
|
||||
use rust_decimal::MathematicalOps;
|
||||
use steel::rvals::SteelVal;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DecimalMathError {
|
||||
#[error("Invalid decimal format: {0}")]
|
||||
InvalidDecimal(String),
|
||||
#[error("Math operation failed: {0}")]
|
||||
MathError(String),
|
||||
#[error("Division by zero")]
|
||||
DivisionByZero,
|
||||
}
|
||||
|
||||
/// Converts a SteelVal to a Decimal
|
||||
fn steel_val_to_decimal(val: &SteelVal) -> Result<Decimal, DecimalMathError> {
|
||||
match val {
|
||||
SteelVal::StringV(s) => {
|
||||
Decimal::from_str(&s.to_string())
|
||||
.map_err(|e| DecimalMathError::InvalidDecimal(format!("{}: {}", s, e)))
|
||||
}
|
||||
SteelVal::NumV(n) => {
|
||||
Decimal::try_from(*n)
|
||||
.map_err(|e| DecimalMathError::InvalidDecimal(format!("{}: {}", n, e)))
|
||||
}
|
||||
SteelVal::IntV(i) => {
|
||||
Ok(Decimal::from(*i))
|
||||
}
|
||||
_ => Err(DecimalMathError::InvalidDecimal(format!("Unsupported type: {:?}", val)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Decimal back to a SteelVal string
|
||||
fn decimal_to_steel_val(decimal: Decimal) -> SteelVal {
|
||||
SteelVal::StringV(decimal.to_string().into())
|
||||
}
|
||||
|
||||
// Basic arithmetic operations
|
||||
pub fn decimal_add(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok((a_dec + b_dec).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_sub(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok((a_dec - b_dec).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_mul(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok((a_dec * b_dec).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_div(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
|
||||
if b_dec.is_zero() {
|
||||
return Err("Division by zero".to_string());
|
||||
}
|
||||
|
||||
Ok((a_dec / b_dec).to_string())
|
||||
}
|
||||
|
||||
// Advanced mathematical functions (requires maths feature)
|
||||
pub fn decimal_pow(base: String, exp: String) -> Result<String, String> {
|
||||
let base_dec = Decimal::from_str(&base).map_err(|e| format!("Invalid decimal '{}': {}", base, e))?;
|
||||
let exp_dec = Decimal::from_str(&exp).map_err(|e| format!("Invalid decimal '{}': {}", exp, e))?;
|
||||
|
||||
base_dec.checked_powd(exp_dec)
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Power operation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_sqrt(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
|
||||
a_dec.sqrt()
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Square root failed (negative number?)".to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_ln(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
|
||||
a_dec.checked_ln()
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Natural log failed (non-positive number?)".to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_log10(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
|
||||
a_dec.checked_log10()
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Log10 failed (non-positive number?)".to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_exp(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
|
||||
a_dec.checked_exp()
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Exponential failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
// Trigonometric functions (input in radians)
|
||||
pub fn decimal_sin(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
|
||||
a_dec.checked_sin()
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Sine calculation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_cos(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
|
||||
a_dec.checked_cos()
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Cosine calculation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_tan(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
|
||||
a_dec.checked_tan()
|
||||
.map(|result| result.to_string())
|
||||
.ok_or_else(|| "Tangent calculation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
// Comparison functions
|
||||
pub fn decimal_gt(a: String, b: String) -> Result<bool, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok(a_dec > b_dec)
|
||||
}
|
||||
|
||||
pub fn decimal_lt(a: String, b: String) -> Result<bool, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok(a_dec < b_dec)
|
||||
}
|
||||
|
||||
pub fn decimal_eq(a: String, b: String) -> Result<bool, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok(a_dec == b_dec)
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
pub fn decimal_abs(a: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
Ok(a_dec.abs().to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_round(a: String, places: i32) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
Ok(a_dec.round_dp(places as u32).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_min(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok(a_dec.min(b_dec).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_max(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok(a_dec.max(b_dec).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_gte(a: String, b: String) -> Result<bool, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok(a_dec >= b_dec)
|
||||
}
|
||||
|
||||
pub fn decimal_lte(a: String, b: String) -> Result<bool, String> {
|
||||
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
|
||||
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
|
||||
Ok(a_dec <= b_dec)
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
// Updated src/steel/server/execution.rs
|
||||
// src/steel/server/execution.rs
|
||||
|
||||
use steel::steel_vm::engine::Engine;
|
||||
use steel::steel_vm::register_fn::RegisterFn;
|
||||
use steel::rvals::SteelVal;
|
||||
use super::functions::SteelContext;
|
||||
use super::decimal_math::*;
|
||||
use super::functions::{SteelContext, convert_row_data_for_steel};
|
||||
use steel_decimal::registry::FunctionRegistry;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error};
|
||||
|
||||
/// Represents different types of values that can be returned from Steel script execution.
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
Strings(Vec<String>),
|
||||
Numbers(Vec<i64>),
|
||||
Mixed(Vec<SteelVal>),
|
||||
}
|
||||
|
||||
/// Errors that can occur during Steel script execution.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ExecutionError {
|
||||
#[error("Script execution failed: {0}")]
|
||||
@@ -25,155 +28,162 @@ pub enum ExecutionError {
|
||||
UnsupportedType(String),
|
||||
}
|
||||
|
||||
pub fn execute_script(
|
||||
/// Creates a Steel execution context with proper boolean value conversion.
|
||||
pub async fn create_steel_context_with_boolean_conversion(
|
||||
current_table: String,
|
||||
schema_id: i64,
|
||||
schema_name: String,
|
||||
mut row_data: HashMap<String, String>,
|
||||
db_pool: Arc<PgPool>,
|
||||
) -> Result<SteelContext, ExecutionError> {
|
||||
// Convert boolean values in row_data to Steel format
|
||||
convert_row_data_for_steel(&db_pool, schema_id, ¤t_table, &mut row_data)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to convert row data for Steel: {}", e);
|
||||
ExecutionError::RuntimeError(format!("Failed to convert row data: {}", e))
|
||||
})?;
|
||||
|
||||
Ok(SteelContext {
|
||||
current_table,
|
||||
schema_id,
|
||||
schema_name,
|
||||
row_data,
|
||||
db_pool,
|
||||
})
|
||||
}
|
||||
|
||||
/// Executes a Steel script with database context and type-safe result processing.
|
||||
pub async fn execute_script(
|
||||
script: String,
|
||||
target_type: &str,
|
||||
_db_pool: Arc<PgPool>, // Passed to the SteelContext
|
||||
context: SteelContext,
|
||||
db_pool: Arc<PgPool>,
|
||||
schema_id: i64,
|
||||
schema_name: String,
|
||||
current_table: String,
|
||||
row_data: HashMap<String, String>,
|
||||
) -> Result<Value, ExecutionError> {
|
||||
let mut vm = Engine::new();
|
||||
|
||||
// Create execution context with proper boolean value conversion
|
||||
let context = create_steel_context_with_boolean_conversion(
|
||||
current_table,
|
||||
schema_id,
|
||||
schema_name,
|
||||
row_data,
|
||||
db_pool.clone(),
|
||||
).await?;
|
||||
|
||||
let context = Arc::new(context);
|
||||
|
||||
// Register existing Steel functions
|
||||
// Register database access functions
|
||||
register_steel_functions(&mut vm, context.clone());
|
||||
|
||||
// Register all decimal math functions
|
||||
|
||||
// Register decimal math operations
|
||||
register_decimal_math_functions(&mut vm);
|
||||
|
||||
// Execute script and process results
|
||||
let results = vm.compile_and_run_raw_program(script)
|
||||
.map_err(|e| ExecutionError::RuntimeError(e.to_string()))?;
|
||||
// Register row data as variables in the Steel VM for get-var access
|
||||
let mut define_script = String::new();
|
||||
|
||||
// Convert results to target type
|
||||
for (key, value) in &context.row_data {
|
||||
// Register only bare variable names for get-var access
|
||||
define_script.push_str(&format!("(define {} \"{}\")\n", key, value));
|
||||
}
|
||||
|
||||
// Execute variable definitions if any exist
|
||||
if !define_script.is_empty() {
|
||||
vm.compile_and_run_raw_program(define_script)
|
||||
.map_err(|e| ExecutionError::RuntimeError(format!("Failed to register variables: {}", e)))?;
|
||||
}
|
||||
|
||||
// Also register variables using the decimal registry as backup method
|
||||
FunctionRegistry::register_variables(&mut vm, context.row_data.clone());
|
||||
|
||||
// Execute the main script
|
||||
let results = vm.compile_and_run_raw_program(script.clone())
|
||||
.map_err(|e| {
|
||||
error!("Steel script execution failed: {}", e);
|
||||
error!("Script was: {}", script);
|
||||
error!("Available variables were: {:?}", context.row_data);
|
||||
ExecutionError::RuntimeError(e.to_string())
|
||||
})?;
|
||||
|
||||
// Convert results to the requested target type
|
||||
match target_type {
|
||||
"STRINGS" => process_string_results(results),
|
||||
_ => Err(ExecutionError::UnsupportedType(target_type.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers Steel functions for database access within the VM context.
|
||||
fn register_steel_functions(vm: &mut Engine, context: Arc<SteelContext>) {
|
||||
// Register steel_get_column with row context
|
||||
debug!("Registering Steel functions with context");
|
||||
|
||||
// Register column access function for current and related tables
|
||||
vm.register_fn("steel_get_column", {
|
||||
let ctx = context.clone();
|
||||
move |table: String, column: String| {
|
||||
debug!("steel_get_column called with table: '{}', column: '{}'", table, column);
|
||||
ctx.steel_get_column(&table, &column)
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| {
|
||||
error!("steel_get_column failed: {:?}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// Register steel_get_column_with_index
|
||||
// Register indexed column access for comma-separated values
|
||||
vm.register_fn("steel_get_column_with_index", {
|
||||
let ctx = context.clone();
|
||||
move |table: String, index: i64, column: String| {
|
||||
debug!("steel_get_column_with_index called with table: '{}', index: {}, column: '{}'", table, index, column);
|
||||
ctx.steel_get_column_with_index(&table, index, &column)
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| {
|
||||
error!("steel_get_column_with_index failed: {:?}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// SQL query registration
|
||||
// Register safe SQL query execution
|
||||
vm.register_fn("steel_query_sql", {
|
||||
let ctx = context.clone();
|
||||
move |query: String| {
|
||||
debug!("steel_query_sql called with query: '{}'", query);
|
||||
ctx.steel_query_sql(&query)
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| {
|
||||
error!("steel_query_sql failed: {:?}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Registers decimal mathematics functions in the Steel VM.
|
||||
fn register_decimal_math_functions(vm: &mut Engine) {
|
||||
// Basic arithmetic operations
|
||||
vm.register_fn("decimal-add", decimal_add);
|
||||
vm.register_fn("decimal-sub", decimal_sub);
|
||||
vm.register_fn("decimal-mul", decimal_mul);
|
||||
vm.register_fn("decimal-div", decimal_div);
|
||||
|
||||
// Advanced mathematical functions
|
||||
vm.register_fn("decimal-pow", decimal_pow);
|
||||
vm.register_fn("decimal-sqrt", decimal_sqrt);
|
||||
vm.register_fn("decimal-ln", decimal_ln);
|
||||
vm.register_fn("decimal-log10", decimal_log10);
|
||||
vm.register_fn("decimal-exp", decimal_exp);
|
||||
|
||||
// Trigonometric functions
|
||||
vm.register_fn("decimal-sin", decimal_sin);
|
||||
vm.register_fn("decimal-cos", decimal_cos);
|
||||
vm.register_fn("decimal-tan", decimal_tan);
|
||||
|
||||
// Comparison functions
|
||||
vm.register_fn("decimal-gt", decimal_gt);
|
||||
vm.register_fn("decimal-lt", decimal_lt);
|
||||
vm.register_fn("decimal-eq", decimal_eq);
|
||||
|
||||
// Utility functions
|
||||
vm.register_fn("decimal-abs", decimal_abs);
|
||||
vm.register_fn("decimal-round", decimal_round);
|
||||
vm.register_fn("decimal-min", decimal_min);
|
||||
vm.register_fn("decimal-max", decimal_max);
|
||||
|
||||
// Additional convenience functions
|
||||
vm.register_fn("decimal-zero", || "0".to_string());
|
||||
vm.register_fn("decimal-one", || "1".to_string());
|
||||
vm.register_fn("decimal-pi", || "3.1415926535897932384626433833".to_string());
|
||||
vm.register_fn("decimal-e", || "2.7182818284590452353602874714".to_string());
|
||||
|
||||
// Type conversion helpers
|
||||
vm.register_fn("to-decimal", |s: String| -> Result<String, String> {
|
||||
use rust_decimal::prelude::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
Decimal::from_str(&s)
|
||||
.map(|d| d.to_string())
|
||||
.map_err(|e| format!("Invalid decimal: {}", e))
|
||||
});
|
||||
|
||||
// Financial functions
|
||||
vm.register_fn("decimal-percentage", |amount: String, percentage: String| -> Result<String, String> {
|
||||
use rust_decimal::prelude::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
let amount_dec = Decimal::from_str(&amount)
|
||||
.map_err(|e| format!("Invalid amount: {}", e))?;
|
||||
let percentage_dec = Decimal::from_str(&percentage)
|
||||
.map_err(|e| format!("Invalid percentage: {}", e))?;
|
||||
let hundred = Decimal::from(100);
|
||||
|
||||
Ok((amount_dec * percentage_dec / hundred).to_string())
|
||||
});
|
||||
|
||||
vm.register_fn("decimal-compound", |principal: String, rate: String, time: String| -> Result<String, String> {
|
||||
use rust_decimal::prelude::*;
|
||||
use rust_decimal::MathematicalOps;
|
||||
use std::str::FromStr;
|
||||
|
||||
let principal_dec = Decimal::from_str(&principal)
|
||||
.map_err(|e| format!("Invalid principal: {}", e))?;
|
||||
let rate_dec = Decimal::from_str(&rate)
|
||||
.map_err(|e| format!("Invalid rate: {}", e))?;
|
||||
let time_dec = Decimal::from_str(&time)
|
||||
.map_err(|e| format!("Invalid time: {}", e))?;
|
||||
|
||||
let one = Decimal::ONE;
|
||||
let compound_factor = (one + rate_dec).checked_powd(time_dec)
|
||||
.ok_or("Compound calculation overflow")?;
|
||||
|
||||
Ok((principal_dec * compound_factor).to_string())
|
||||
});
|
||||
debug!("Registering decimal math functions");
|
||||
FunctionRegistry::register_all(vm);
|
||||
}
|
||||
|
||||
/// Processes Steel script results into string format for consistent output.
|
||||
fn process_string_results(results: Vec<SteelVal>) -> Result<Value, ExecutionError> {
|
||||
let mut strings = Vec::new();
|
||||
|
||||
for result in results {
|
||||
match result {
|
||||
SteelVal::StringV(s) => strings.push(s.to_string()),
|
||||
SteelVal::NumV(n) => strings.push(n.to_string()),
|
||||
SteelVal::IntV(i) => strings.push(i.to_string()),
|
||||
SteelVal::BoolV(b) => strings.push(b.to_string()),
|
||||
let result_str = match result {
|
||||
SteelVal::StringV(s) => s.to_string(),
|
||||
SteelVal::NumV(n) => n.to_string(),
|
||||
SteelVal::IntV(i) => i.to_string(),
|
||||
SteelVal::BoolV(b) => b.to_string(),
|
||||
_ => {
|
||||
error!("Unexpected result type: {:?}", result);
|
||||
return Err(ExecutionError::TypeConversionError(
|
||||
format!("Expected string-convertible type, got {:?}", result)
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
strings.push(result_str);
|
||||
}
|
||||
|
||||
Ok(Value::Strings(strings))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// src/steel/server/functions.rs
|
||||
|
||||
use steel::rvals::SteelVal;
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
@@ -16,8 +17,14 @@ pub enum FunctionError {
|
||||
TableNotFound(String),
|
||||
#[error("Database error: {0}")]
|
||||
DatabaseError(String),
|
||||
#[error("Prohibited data type access: {0}")]
|
||||
ProhibitedTypeAccess(String),
|
||||
}
|
||||
|
||||
/// Data types that Steel scripts are prohibited from accessing for security reasons
|
||||
const PROHIBITED_TYPES: &[&str] = &["BIGINT", "DATE", "TIMESTAMPTZ"];
|
||||
|
||||
/// Execution context for Steel scripts with database access capabilities.
|
||||
#[derive(Clone)]
|
||||
pub struct SteelContext {
|
||||
pub current_table: String,
|
||||
@@ -28,6 +35,8 @@ pub struct SteelContext {
|
||||
}
|
||||
|
||||
impl SteelContext {
|
||||
/// Resolves a base table name to its full qualified name in the current schema.
|
||||
/// Used for foreign key relationship traversal in Steel scripts.
|
||||
pub async fn get_related_table_name(&self, base_name: &str) -> Result<String, FunctionError> {
|
||||
let table_def = sqlx::query!(
|
||||
r#"SELECT table_name FROM table_definitions
|
||||
@@ -43,13 +52,108 @@ impl SteelContext {
|
||||
Ok(table_def.table_name)
|
||||
}
|
||||
|
||||
/// Retrieves the SQL data type for a specific column in a table.
|
||||
/// Parses the JSON column definitions to find type information.
|
||||
async fn get_column_type(&self, table_name: &str, column_name: &str) -> Result<String, FunctionError> {
|
||||
let table_def = sqlx::query!(
|
||||
r#"SELECT columns FROM table_definitions
|
||||
WHERE schema_id = $1 AND table_name = $2"#,
|
||||
self.schema_id,
|
||||
table_name
|
||||
)
|
||||
.fetch_optional(&*self.db_pool)
|
||||
.await
|
||||
.map_err(|e| FunctionError::DatabaseError(e.to_string()))?
|
||||
.ok_or_else(|| FunctionError::TableNotFound(table_name.to_string()))?;
|
||||
|
||||
let columns: Vec<String> = serde_json::from_value(table_def.columns)
|
||||
.map_err(|e| FunctionError::DatabaseError(format!("Invalid column data: {}", e)))?;
|
||||
|
||||
// Parse column definitions to find the requested column type
|
||||
for column_def in columns {
|
||||
let mut parts = column_def.split_whitespace();
|
||||
if let (Some(name), Some(data_type)) = (parts.next(), parts.next()) {
|
||||
let column_name_clean = name.trim_matches('"');
|
||||
if column_name_clean == column_name {
|
||||
return Ok(data_type.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(FunctionError::ColumnNotFound(format!(
|
||||
"Column '{}' not found in table '{}'",
|
||||
column_name,
|
||||
table_name
|
||||
)))
|
||||
}
|
||||
|
||||
/// Converts database values to Steel script format based on column type.
|
||||
/// Currently handles boolean conversion to Steel's #true/#false syntax.
|
||||
fn convert_value_to_steel_format(&self, value: &str, column_type: &str) -> String {
|
||||
let normalized_type = normalize_data_type(column_type);
|
||||
|
||||
match normalized_type.as_str() {
|
||||
"BOOLEAN" | "BOOL" => {
|
||||
// Convert database boolean representations to Steel boolean syntax
|
||||
match value.to_lowercase().as_str() {
|
||||
"true" | "t" | "1" | "yes" | "on" => "#true".to_string(),
|
||||
"false" | "f" | "0" | "no" | "off" => "#false".to_string(),
|
||||
_ => value.to_string(), // Return as-is if not a recognized boolean
|
||||
}
|
||||
}
|
||||
"INTEGER" => value.to_string(),
|
||||
_ => value.to_string(), // Return as-is for other types
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates that a column type is allowed for Steel script access.
|
||||
/// Returns the column type if validation passes, error if prohibited.
|
||||
async fn validate_column_type_and_get_type(&self, table_name: &str, column_name: &str) -> Result<String, FunctionError> {
|
||||
let column_type = self.get_column_type(table_name, column_name).await?;
|
||||
|
||||
if is_prohibited_type(&column_type) {
|
||||
return Err(FunctionError::ProhibitedTypeAccess(format!(
|
||||
"Cannot access column '{}' in table '{}' because it has prohibited type '{}'. Steel scripts cannot access columns of type: {}",
|
||||
column_name,
|
||||
table_name,
|
||||
column_type,
|
||||
PROHIBITED_TYPES.join(", ")
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(column_type)
|
||||
}
|
||||
|
||||
/// Retrieves column value from current table or related tables via foreign keys.
|
||||
///
|
||||
/// # Behavior
|
||||
/// - Current table: Returns value directly from row_data with type conversion
|
||||
/// - Related table: Follows foreign key relationship and queries database
|
||||
/// - All accesses are subject to prohibited type validation
|
||||
pub fn steel_get_column(&self, table: &str, column: &str) -> Result<SteelVal, SteelVal> {
|
||||
if table == self.current_table {
|
||||
// Access current table data with type validation
|
||||
let column_type = tokio::task::block_in_place(|| {
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
handle.block_on(async {
|
||||
self.validate_column_type_and_get_type(table, column).await
|
||||
})
|
||||
});
|
||||
|
||||
let column_type = match column_type {
|
||||
Ok(ct) => ct,
|
||||
Err(e) => return Err(SteelVal::StringV(e.to_string().into())),
|
||||
};
|
||||
|
||||
return self.row_data.get(column)
|
||||
.map(|v| SteelVal::StringV(v.clone().into()))
|
||||
.map(|v| {
|
||||
let converted_value = self.convert_value_to_steel_format(v, &column_type);
|
||||
SteelVal::StringV(converted_value.into())
|
||||
})
|
||||
.ok_or_else(|| SteelVal::StringV(format!("Column {} not found", column).into()));
|
||||
}
|
||||
|
||||
// Access related table via foreign key relationship
|
||||
let base_name = table.split_once('_')
|
||||
.map(|(_, rest)| rest)
|
||||
.unwrap_or(table);
|
||||
@@ -58,60 +162,103 @@ impl SteelContext {
|
||||
let fk_value = self.row_data.get(&fk_column)
|
||||
.ok_or_else(|| SteelVal::StringV(format!("Foreign key {} not found", fk_column).into()))?;
|
||||
|
||||
// Use `tokio::task::block_in_place` to safely block the thread
|
||||
let result = tokio::task::block_in_place(|| {
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
handle.block_on(async {
|
||||
let actual_table = self.get_related_table_name(base_name).await
|
||||
.map_err(|e| SteelVal::StringV(e.to_string().into()))?;
|
||||
|
||||
// Add quotes around the table name
|
||||
sqlx::query_scalar::<_, String>(
|
||||
// Validate column type and get type information
|
||||
let column_type = self.validate_column_type_and_get_type(&actual_table, column).await
|
||||
.map_err(|e| SteelVal::StringV(e.to_string().into()))?;
|
||||
|
||||
// Query the related table for the column value
|
||||
let raw_value = sqlx::query_scalar::<_, String>(
|
||||
&format!("SELECT {} FROM \"{}\".\"{}\" WHERE id = $1", column, self.schema_name, actual_table)
|
||||
)
|
||||
.bind(fk_value.parse::<i64>().map_err(|_|
|
||||
SteelVal::StringV("Invalid foreign key format".into()))?)
|
||||
.fetch_one(&*self.db_pool)
|
||||
.await
|
||||
.map_err(|e| SteelVal::StringV(e.to_string().into()))
|
||||
.map_err(|e| SteelVal::StringV(e.to_string().into()))?;
|
||||
|
||||
// Convert to appropriate Steel format
|
||||
let converted_value = self.convert_value_to_steel_format(&raw_value, &column_type);
|
||||
Ok(converted_value)
|
||||
})
|
||||
});
|
||||
|
||||
result.map(|v| SteelVal::StringV(v.into()))
|
||||
}
|
||||
|
||||
/// Retrieves a specific indexed element from a comma-separated column value.
|
||||
/// Useful for accessing elements from array-like string representations.
|
||||
pub fn steel_get_column_with_index(
|
||||
&self,
|
||||
table: &str,
|
||||
index: i64,
|
||||
column: &str
|
||||
) -> Result<SteelVal, SteelVal> {
|
||||
// Get the full value with proper type conversion
|
||||
let value = self.steel_get_column(table, column)?;
|
||||
|
||||
if let SteelVal::StringV(s) = value {
|
||||
let parts: Vec<_> = s.split(',').collect();
|
||||
parts.get(index as usize)
|
||||
.map(|v| SteelVal::StringV(v.trim().into()))
|
||||
.ok_or_else(|| SteelVal::StringV("Index out of bounds".into()))
|
||||
|
||||
if let Some(part) = parts.get(index as usize) {
|
||||
let trimmed_part = part.trim();
|
||||
|
||||
// Apply type conversion to the indexed part based on original column type
|
||||
let column_type = tokio::task::block_in_place(|| {
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
handle.block_on(async {
|
||||
self.get_column_type(table, column).await
|
||||
})
|
||||
});
|
||||
|
||||
match column_type {
|
||||
Ok(ct) => {
|
||||
let converted_part = self.convert_value_to_steel_format(trimmed_part, &ct);
|
||||
Ok(SteelVal::StringV(converted_part.into()))
|
||||
}
|
||||
Err(_) => {
|
||||
// If type cannot be determined, return value as-is
|
||||
Ok(SteelVal::StringV(trimmed_part.into()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(SteelVal::StringV("Index out of bounds".into()))
|
||||
}
|
||||
} else {
|
||||
Err(SteelVal::StringV("Expected comma-separated string".into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes read-only SQL queries from Steel scripts with safety restrictions.
|
||||
///
|
||||
/// # Security Features
|
||||
/// - Only SELECT, SHOW, and EXPLAIN queries allowed
|
||||
/// - Prohibited column type access validation
|
||||
/// - Returns first column of all rows as comma-separated string
|
||||
pub fn steel_query_sql(&self, query: &str) -> Result<SteelVal, SteelVal> {
|
||||
// Validate query is read-only
|
||||
if !is_read_only_query(query) {
|
||||
return Err(SteelVal::StringV(
|
||||
"Only SELECT queries are allowed".into()
|
||||
));
|
||||
}
|
||||
|
||||
if contains_prohibited_column_access(query) {
|
||||
return Err(SteelVal::StringV(format!(
|
||||
"SQL query may access prohibited column types. Steel scripts cannot access columns of type: {}",
|
||||
PROHIBITED_TYPES.join(", ")
|
||||
).into()));
|
||||
}
|
||||
|
||||
let pool = self.db_pool.clone();
|
||||
|
||||
// Use `tokio::task::block_in_place` to safely block the thread
|
||||
let result = tokio::task::block_in_place(|| {
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
handle.block_on(async {
|
||||
// Execute and get first column of all rows as strings
|
||||
let rows = sqlx::query(query)
|
||||
.fetch_all(&*pool)
|
||||
.await
|
||||
@@ -132,9 +279,85 @@ impl SteelContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a data type is prohibited for Steel script access.
|
||||
fn is_prohibited_type(data_type: &str) -> bool {
|
||||
let normalized_type = normalize_data_type(data_type);
|
||||
PROHIBITED_TYPES.iter().any(|&prohibited| normalized_type.starts_with(prohibited))
|
||||
}
|
||||
|
||||
/// Normalizes data type strings for consistent comparison.
|
||||
/// Handles variations like NUMERIC(10,2) by extracting base type.
|
||||
fn normalize_data_type(data_type: &str) -> String {
|
||||
data_type.to_uppercase()
|
||||
.split('(') // Remove precision/scale from NUMERIC(x,y)
|
||||
.next()
|
||||
.unwrap_or(data_type)
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Performs basic heuristic check for prohibited column type access in SQL queries.
|
||||
/// Looks for common patterns that might indicate access to restricted types.
|
||||
fn contains_prohibited_column_access(query: &str) -> bool {
|
||||
let query_upper = query.to_uppercase();
|
||||
|
||||
let patterns = [
|
||||
"EXTRACT(", // Common with DATE/TIMESTAMPTZ
|
||||
"DATE_PART(", // Common with DATE/TIMESTAMPTZ
|
||||
"::DATE",
|
||||
"::TIMESTAMPTZ",
|
||||
"::BIGINT",
|
||||
];
|
||||
|
||||
patterns.iter().any(|pattern| query_upper.contains(pattern))
|
||||
}
|
||||
|
||||
/// Validates that a query is read-only and safe for Steel script execution.
|
||||
fn is_read_only_query(query: &str) -> bool {
|
||||
let query = query.trim_start().to_uppercase();
|
||||
query.starts_with("SELECT") ||
|
||||
query.starts_with("SHOW") ||
|
||||
query.starts_with("EXPLAIN")
|
||||
}
|
||||
|
||||
/// Converts row data boolean values to Steel script format during context initialization.
|
||||
pub async fn convert_row_data_for_steel(
|
||||
db_pool: &PgPool,
|
||||
schema_id: i64,
|
||||
table_name: &str,
|
||||
row_data: &mut HashMap<String, String>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let table_def = sqlx::query!(
|
||||
r#"SELECT columns FROM table_definitions
|
||||
WHERE schema_id = $1 AND table_name = $2"#,
|
||||
schema_id,
|
||||
table_name
|
||||
)
|
||||
.fetch_optional(db_pool)
|
||||
.await?
|
||||
.ok_or_else(|| sqlx::Error::RowNotFound)?;
|
||||
|
||||
// Parse column definitions to identify boolean columns for conversion
|
||||
if let Ok(columns) = serde_json::from_value::<Vec<String>>(table_def.columns) {
|
||||
for column_def in columns {
|
||||
let mut parts = column_def.split_whitespace();
|
||||
if let (Some(name), Some(data_type)) = (parts.next(), parts.next()) {
|
||||
let column_name = name.trim_matches('"');
|
||||
let normalized_type = normalize_data_type(data_type);
|
||||
|
||||
if normalized_type == "BOOLEAN" || normalized_type == "BOOL" {
|
||||
if let Some(value) = row_data.get_mut(column_name) {
|
||||
// Convert boolean value to Steel format
|
||||
*value = match value.to_lowercase().as_str() {
|
||||
"true" | "t" | "1" | "yes" | "on" => "#true".to_string(),
|
||||
"false" | "f" | "0" | "no" | "off" => "#false".to_string(),
|
||||
_ => value.clone(), // Keep original if not recognized
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// src/steel/server/mod.rs
|
||||
pub mod execution;
|
||||
pub mod syntax_parser;
|
||||
pub mod functions;
|
||||
pub mod decimal_math;
|
||||
|
||||
pub use execution::*;
|
||||
pub use syntax_parser::*;
|
||||
pub use functions::*;
|
||||
pub use decimal_math::*;
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct SyntaxParser {
|
||||
// Existing patterns for column/SQL integration
|
||||
current_table_column_re: Regex,
|
||||
different_table_column_re: Regex,
|
||||
one_to_many_indexed_re: Regex,
|
||||
sql_integration_re: Regex,
|
||||
|
||||
// Simple math operation replacement patterns
|
||||
math_operators: Vec<(Regex, &'static str)>,
|
||||
number_literal_re: Regex,
|
||||
}
|
||||
|
||||
impl SyntaxParser {
|
||||
pub fn new() -> Self {
|
||||
// Define math operator replacements
|
||||
let math_operators = vec![
|
||||
// Basic arithmetic
|
||||
(Regex::new(r"\(\s*\+\s+").unwrap(), "(decimal-add "),
|
||||
(Regex::new(r"\(\s*-\s+").unwrap(), "(decimal-sub "),
|
||||
(Regex::new(r"\(\s*\*\s+").unwrap(), "(decimal-mul "),
|
||||
(Regex::new(r"\(\s*/\s+").unwrap(), "(decimal-div "),
|
||||
|
||||
// Power and advanced operations
|
||||
(Regex::new(r"\(\s*\^\s+").unwrap(), "(decimal-pow "),
|
||||
(Regex::new(r"\(\s*\*\*\s+").unwrap(), "(decimal-pow "),
|
||||
(Regex::new(r"\(\s*pow\s+").unwrap(), "(decimal-pow "),
|
||||
(Regex::new(r"\(\s*sqrt\s+").unwrap(), "(decimal-sqrt "),
|
||||
|
||||
// Logarithmic functions
|
||||
(Regex::new(r"\(\s*ln\s+").unwrap(), "(decimal-ln "),
|
||||
(Regex::new(r"\(\s*log\s+").unwrap(), "(decimal-ln "),
|
||||
(Regex::new(r"\(\s*log10\s+").unwrap(), "(decimal-log10 "),
|
||||
(Regex::new(r"\(\s*exp\s+").unwrap(), "(decimal-exp "),
|
||||
|
||||
// Trigonometric functions
|
||||
(Regex::new(r"\(\s*sin\s+").unwrap(), "(decimal-sin "),
|
||||
(Regex::new(r"\(\s*cos\s+").unwrap(), "(decimal-cos "),
|
||||
(Regex::new(r"\(\s*tan\s+").unwrap(), "(decimal-tan "),
|
||||
|
||||
// Comparison operators
|
||||
(Regex::new(r"\(\s*>\s+").unwrap(), "(decimal-gt "),
|
||||
(Regex::new(r"\(\s*<\s+").unwrap(), "(decimal-lt "),
|
||||
(Regex::new(r"\(\s*=\s+").unwrap(), "(decimal-eq "),
|
||||
(Regex::new(r"\(\s*>=\s+").unwrap(), "(decimal-gte "),
|
||||
(Regex::new(r"\(\s*<=\s+").unwrap(), "(decimal-lte "),
|
||||
|
||||
// Utility functions
|
||||
(Regex::new(r"\(\s*abs\s+").unwrap(), "(decimal-abs "),
|
||||
(Regex::new(r"\(\s*min\s+").unwrap(), "(decimal-min "),
|
||||
(Regex::new(r"\(\s*max\s+").unwrap(), "(decimal-max "),
|
||||
(Regex::new(r"\(\s*round\s+").unwrap(), "(decimal-round "),
|
||||
];
|
||||
|
||||
SyntaxParser {
|
||||
current_table_column_re: Regex::new(r"@(\w+)").unwrap(),
|
||||
different_table_column_re: Regex::new(r"@(\w+)\.(\w+)").unwrap(),
|
||||
one_to_many_indexed_re: Regex::new(r"@(\w+)\[(\d+)\]\.(\w+)").unwrap(),
|
||||
sql_integration_re: Regex::new(r#"@sql\((['"])(.*?)['"]\)"#).unwrap(),
|
||||
|
||||
// FIXED: Match negative numbers and avoid already quoted strings
|
||||
number_literal_re: Regex::new(r#"(?<!")(-?\d+\.?\d*(?:[eE][+-]?\d+)?)(?!")"#).unwrap(),
|
||||
|
||||
math_operators,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&self, script: &str, current_table: &str) -> String {
|
||||
let mut transformed = script.to_string();
|
||||
|
||||
// Step 1: Convert all numeric literals to strings (FIXED to handle negative numbers)
|
||||
transformed = self.convert_numbers_to_strings(&transformed);
|
||||
|
||||
// Step 2: Replace math function calls with decimal equivalents (SIMPLIFIED)
|
||||
transformed = self.replace_math_functions(&transformed);
|
||||
|
||||
// Step 3: Handle existing column and SQL integrations (unchanged)
|
||||
transformed = self.process_column_integrations(&transformed, current_table);
|
||||
|
||||
transformed
|
||||
}
|
||||
|
||||
/// Convert all unquoted numeric literals to quoted strings
|
||||
fn convert_numbers_to_strings(&self, script: &str) -> String {
|
||||
// This regex matches numbers that are NOT already inside quotes
|
||||
self.number_literal_re.replace_all(script, |caps: ®ex::Captures| {
|
||||
format!("\"{}\"", &caps[1])
|
||||
}).to_string()
|
||||
}
|
||||
|
||||
/// Replace math function calls with decimal equivalents (SIMPLIFIED)
|
||||
fn replace_math_functions(&self, script: &str) -> String {
|
||||
let mut result = script.to_string();
|
||||
|
||||
// Apply all math operator replacements
|
||||
for (pattern, replacement) in &self.math_operators {
|
||||
result = pattern.replace_all(&result, *replacement).to_string();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Process existing column and SQL integrations (unchanged logic)
|
||||
fn process_column_integrations(&self, script: &str, current_table: &str) -> String {
|
||||
let mut transformed = script.to_string();
|
||||
|
||||
// Process indexed access first to avoid overlap with relationship matches
|
||||
transformed = self.one_to_many_indexed_re.replace_all(&transformed, |caps: ®ex::Captures| {
|
||||
format!("(steel_get_column_with_index \"{}\" {} \"{}\")",
|
||||
&caps[1], &caps[2], &caps[3])
|
||||
}).to_string();
|
||||
|
||||
// Process relationships
|
||||
transformed = self.different_table_column_re.replace_all(&transformed, |caps: ®ex::Captures| {
|
||||
format!("(steel_get_column \"{}\" \"{}\")", &caps[1], &caps[2])
|
||||
}).to_string();
|
||||
|
||||
// Process basic column access
|
||||
transformed = self.current_table_column_re.replace_all(&transformed, |caps: ®ex::Captures| {
|
||||
format!("(steel_get_column \"{}\" \"{}\")", current_table, &caps[1])
|
||||
}).to_string();
|
||||
|
||||
// Process SQL integration
|
||||
transformed = self.sql_integration_re.replace_all(&transformed, |caps: ®ex::Captures| {
|
||||
format!("(steel_query_sql \"{}\")", &caps[2])
|
||||
}).to_string();
|
||||
|
||||
transformed
|
||||
}
|
||||
|
||||
pub fn extract_dependencies(&self, script: &str, current_table: &str) -> (HashSet<String>, HashSet<String>) {
|
||||
let mut tables = HashSet::new();
|
||||
let mut columns = HashSet::new();
|
||||
|
||||
for cap in self.current_table_column_re.captures_iter(script) {
|
||||
tables.insert(current_table.to_string());
|
||||
columns.insert(cap[1].to_string());
|
||||
}
|
||||
|
||||
for cap in self.different_table_column_re.captures_iter(script) {
|
||||
tables.insert(cap[1].to_string());
|
||||
columns.insert(cap[2].to_string());
|
||||
}
|
||||
|
||||
for cap in self.one_to_many_indexed_re.captures_iter(script) {
|
||||
tables.insert(cap[1].to_string());
|
||||
columns.insert(cap[3].to_string());
|
||||
}
|
||||
|
||||
(tables, columns)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
"indexes": ["company_name", "is_active"],
|
||||
"profile_name": "default",
|
||||
"linked_table_name": ""
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
|
||||
ERROR:
|
||||
Code: NotFound
|
||||
Message: Linked table not found in profile
|
||||
@@ -35,12 +35,12 @@ ERROR:
|
||||
],
|
||||
"indexes": ["company_name", "is_active"],
|
||||
"profile_name": "default"
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
|
||||
{
|
||||
"success": true,
|
||||
"sql": "CREATE TABLE \"2025_company_data1\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n firma TEXT NOT NULL,\n \"company_name\" TEXT,\n \"textfield\" TEXT,\n \"textfield2\" TEXT,\n \"textfield3\" TEXT,\n \"headquarters_psc\" TEXT,\n \"contact_phone\" VARCHAR(15),\n \"office_address\" TEXT,\n \"support_email\" VARCHAR(255),\n \"is_active\" BOOLEAN,\n \"last_updated\" TIMESTAMPTZ,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_company_data1_firma ON \"2025_company_data1\" (firma)\nCREATE INDEX idx_2025_company_data1_company_name ON \"2025_company_data1\" (\"company_name\")\nCREATE INDEX idx_2025_company_data1_is_active ON \"2025_company_data1\" (\"is_active\")"
|
||||
}
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
@@ -56,12 +56,12 @@ ERROR:
|
||||
❯ grpcurl -plaintext -d '{
|
||||
"profile_name": "default",
|
||||
"table_name": "2025_company_data1"
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/DeleteTable
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/DeleteTable
|
||||
{
|
||||
"success": true,
|
||||
"message": "Table '2025_company_data1' and its definition were successfully removed"
|
||||
}
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
|
||||
{}
|
||||
╭─ ~/Doc/pr/multieko2/server main ⇡1 ········· ✔
|
||||
╭─ ~/Doc/pr/komp_ac/server main ⇡1 ········· ✔
|
||||
╰─
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
@@ -80,12 +80,12 @@
|
||||
"indexes": ["column1", "columnd"],
|
||||
"profile_name": "new_profile",
|
||||
"linked_table_name": "2025_multi_dependent_table3"
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
|
||||
{
|
||||
"success": true,
|
||||
"sql": "CREATE TABLE \"2025_multi_dependent_table5\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n firma TEXT NOT NULL,\n \"multi_dependent_table3_id\" BIGINT NOT NULL REFERENCES \"2025_multi_dependent_table3\"(id),\n \"2025_column1\" TEXT,\n \"2025_columnx\" TEXT,\n \"2025_columny\" TEXT,\n \"2025_columnz\" TEXT,\n \"2025_columna\" TEXT,\n \"2025_columnb\" TEXT,\n \"2025_columnc\" TEXT,\n \"2025_columnd\" TEXT,\n \"2025_column2\" INTEGER,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_multi_dependent_table5_firma ON \"2025_multi_dependent_table5\" (firma)\nCREATE INDEX idx_2025_multi_dependent_table5_multi_dependent_table3_id ON \"2025_multi_dependent_table5\" (\"multi_dependent_table3_id\")\nCREATE INDEX idx_2025_multi_dependent_table5_2025_column1 ON \"2025_multi_dependent_table5\" (\"2025_column1\")\nCREATE INDEX idx_2025_multi_dependent_table5_2025_columnd ON \"2025_multi_dependent_table5\" (\"2025_columnd\")"
|
||||
}
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
@@ -44,12 +44,12 @@
|
||||
],
|
||||
"indexes": ["firma", "mesto"],
|
||||
"profile_name": "default"
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
|
||||
{
|
||||
"success": true,
|
||||
"sql": "CREATE TABLE \"2025_adresar6\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n \"firma\" TEXT,\n \"kz\" TEXT,\n \"drc\" TEXT,\n \"ulica\" TEXT,\n \"psc\" TEXT,\n \"mesto\" TEXT,\n \"stat\" TEXT,\n \"banka\" TEXT,\n \"ucet\" TEXT,\n \"skladm\" TEXT,\n \"ico\" TEXT,\n \"kontakt\" TEXT,\n \"telefon\" VARCHAR(15),\n \"skladu\" TEXT,\n \"fax\" TEXT,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_adresar6_firma ON \"2025_adresar6\" (\"firma\")\nCREATE INDEX idx_2025_adresar6_mesto ON \"2025_adresar6\" (\"mesto\")"
|
||||
}
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
@@ -154,4 +154,4 @@ grpcurl -plaintext -d '{
|
||||
{"linked_table_name": "2025_shipping_provider", "required": false},
|
||||
{"linked_table_name": "2025_ecom_product", "required": false}
|
||||
]
|
||||
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
|
||||
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/table_definition/handlers/delete_table.rs
|
||||
use tonic::Status;
|
||||
use sqlx::PgPool;
|
||||
use common::proto::multieko2::table_definition::{DeleteTableRequest, DeleteTableResponse};
|
||||
use common::proto::komp_ac::table_definition::{DeleteTableRequest, DeleteTableResponse};
|
||||
|
||||
pub async fn delete_table(
|
||||
db_pool: &PgPool,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/table_definition/handlers/get_profile_tree.rs
|
||||
use tonic::{Request, Response, Status};
|
||||
use sqlx::PgPool;
|
||||
use common::proto::multieko2::{
|
||||
use common::proto::komp_ac::{
|
||||
common::Empty,
|
||||
table_definition::{
|
||||
ProfileTreeResponse,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use tonic::Status;
|
||||
use sqlx::{PgPool, Transaction, Postgres};
|
||||
use serde_json::json;
|
||||
use common::proto::multieko2::table_definition::{PostTableDefinitionRequest, TableDefinitionResponse};
|
||||
use common::proto::komp_ac::table_definition::{PostTableDefinitionRequest, TableDefinitionResponse};
|
||||
|
||||
const PREDEFINED_FIELD_TYPES: &[(&str, &str)] = &[
|
||||
("text", "TEXT"),
|
||||
|
||||
@@ -4,7 +4,7 @@ This is how we can push script into the database, now its stored after a push in
|
||||
"target_column": "fax",
|
||||
"script": "(set! fax telefon)",
|
||||
"description": "Copy telefon value to fax column"
|
||||
}' localhost:50051 multieko2.table_script.TableScript/PostTableScript
|
||||
}' localhost:50051 komp_ac.table_script.TableScript/PostTableScript
|
||||
{
|
||||
"id": "2"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
// src/table_script/handlers.rs
|
||||
pub mod dependency_analyzer;
|
||||
pub mod dependency_utils;
|
||||
pub mod post_table_script;
|
||||
|
||||
pub use post_table_script::post_table_script;
|
||||
pub use dependency_analyzer::{DependencyAnalyzer, DependencyError, Dependency, DependencyType};
|
||||
pub use dependency_utils::*;
|
||||
pub use post_table_script::*;
|
||||
|
||||
566
server/src/table_script/handlers/dependency_analyzer.rs
Normal file
566
server/src/table_script/handlers/dependency_analyzer.rs
Normal file
@@ -0,0 +1,566 @@
|
||||
// src/table_script/handlers/dependency_analyzer.rs
|
||||
|
||||
use std::collections::HashMap;
|
||||
use tonic::Status;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
/// Represents the state of a node during dependency graph traversal.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum NodeState {
|
||||
Unvisited,
|
||||
Visiting, // Currently in recursion stack
|
||||
Visited, // Completely processed
|
||||
}
|
||||
|
||||
/// Represents a dependency relationship between tables in Steel scripts.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dependency {
|
||||
pub target_table: String,
|
||||
pub dependency_type: DependencyType,
|
||||
pub context: Option<Value>,
|
||||
}
|
||||
|
||||
/// Types of dependencies that can exist between tables in Steel scripts.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DependencyType {
|
||||
/// Direct column access via steel_get_column
|
||||
ColumnAccess { column: String },
|
||||
/// Indexed column access via steel_get_column_with_index
|
||||
IndexedAccess { column: String, index: i64 },
|
||||
/// Raw SQL query access via steel_query_sql
|
||||
SqlQuery { query_fragment: String },
|
||||
}
|
||||
|
||||
impl DependencyType {
|
||||
/// Returns the string representation used in the database.
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
DependencyType::ColumnAccess { .. } => "column_access",
|
||||
DependencyType::IndexedAccess { .. } => "indexed_access",
|
||||
DependencyType::SqlQuery { .. } => "sql_query",
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates context JSON for database storage.
|
||||
pub fn context_json(&self) -> Value {
|
||||
match self {
|
||||
DependencyType::ColumnAccess { column } => {
|
||||
json!({ "column": column })
|
||||
}
|
||||
DependencyType::IndexedAccess { column, index } => {
|
||||
json!({ "column": column, "index": index })
|
||||
}
|
||||
DependencyType::SqlQuery { query_fragment } => {
|
||||
json!({ "query_fragment": query_fragment })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur during dependency analysis.
|
||||
#[derive(Debug)]
|
||||
pub enum DependencyError {
|
||||
CircularDependency {
|
||||
cycle_path: Vec<String>,
|
||||
involving_script: String
|
||||
},
|
||||
InvalidTableReference {
|
||||
table_name: String,
|
||||
script_context: String
|
||||
},
|
||||
ScriptParseError {
|
||||
error: String
|
||||
},
|
||||
DatabaseError {
|
||||
error: String
|
||||
},
|
||||
}
|
||||
|
||||
impl From<DependencyError> for Status {
|
||||
fn from(error: DependencyError) -> Self {
|
||||
match error {
|
||||
DependencyError::CircularDependency { cycle_path, involving_script } => {
|
||||
Status::failed_precondition(format!(
|
||||
"Circular dependency detected in script for '{}': {}",
|
||||
involving_script,
|
||||
cycle_path.join(" -> ")
|
||||
))
|
||||
}
|
||||
DependencyError::InvalidTableReference { table_name, script_context } => {
|
||||
Status::not_found(format!(
|
||||
"Table '{}' referenced in script '{}' does not exist",
|
||||
table_name, script_context
|
||||
))
|
||||
}
|
||||
DependencyError::ScriptParseError { error } => {
|
||||
Status::invalid_argument(format!("Script parsing failed: {}", error))
|
||||
}
|
||||
DependencyError::DatabaseError { error } => {
|
||||
Status::internal(format!("Database error: {}", error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Analyzes Steel scripts to extract table dependencies and validate referential integrity.
|
||||
///
|
||||
/// This analyzer identifies how tables reference each other through Steel function calls
|
||||
/// and ensures that dependency graphs remain acyclic while respecting table link constraints.
|
||||
pub struct DependencyAnalyzer {
|
||||
schema_id: i64,
|
||||
}
|
||||
|
||||
impl DependencyAnalyzer {
|
||||
/// Creates a new dependency analyzer for the specified schema.
|
||||
pub fn new(schema_id: i64) -> Self {
|
||||
Self { schema_id }
|
||||
}
|
||||
|
||||
/// Analyzes a Steel script to extract all table dependencies.
|
||||
///
|
||||
/// Uses regex patterns to identify function calls that create dependencies:
|
||||
/// - `steel_get_column` calls for direct column access
|
||||
/// - `steel_get_column_with_index` calls for indexed access
|
||||
/// - `steel_query_sql` calls for raw SQL access
|
||||
/// - `get-var` calls in transformed scripts
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `script` - The Steel script code to analyze
|
||||
/// * `current_table_name` - Name of the table this script belongs to (for self-references)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(Vec<Dependency>)` - List of identified dependencies
|
||||
/// * `Err(DependencyError)` - If script parsing fails
|
||||
pub fn analyze_script_dependencies(&self, script: &str, current_table_name: &str) -> Result<Vec<Dependency>, DependencyError> {
|
||||
let mut dependencies = Vec::new();
|
||||
|
||||
// Extract different types of dependencies using regex patterns
|
||||
dependencies.extend(self.extract_function_calls(script)?);
|
||||
dependencies.extend(self.extract_sql_dependencies(script)?);
|
||||
dependencies.extend(self.extract_get_var_calls(script, current_table_name)?);
|
||||
|
||||
Ok(dependencies)
|
||||
}
|
||||
|
||||
/// Extracts Steel function calls that create table dependencies.
|
||||
fn extract_function_calls(&self, script: &str) -> Result<Vec<Dependency>, DependencyError> {
|
||||
let mut dependencies = Vec::new();
|
||||
|
||||
// Pattern: (steel_get_column "table" "column")
|
||||
let column_pattern = regex::Regex::new(r#"\(\s*steel_get_column\s+"([^"]+)"\s+"([^"]+)""#)
|
||||
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
|
||||
|
||||
for caps in column_pattern.captures_iter(script) {
|
||||
let table = caps[1].to_string();
|
||||
let column = caps[2].to_string();
|
||||
dependencies.push(Dependency {
|
||||
target_table: table,
|
||||
dependency_type: DependencyType::ColumnAccess { column },
|
||||
context: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Pattern: (steel_get_column_with_index "table" index "column")
|
||||
let indexed_pattern = regex::Regex::new(r#"\(\s*steel_get_column_with_index\s+"([^"]+)"\s+(\d+)\s+"([^"]+)""#)
|
||||
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
|
||||
|
||||
for caps in indexed_pattern.captures_iter(script) {
|
||||
let table = caps[1].to_string();
|
||||
let index: i64 = caps[2].parse()
|
||||
.map_err(|e: std::num::ParseIntError| DependencyError::ScriptParseError { error: e.to_string() })?;
|
||||
let column = caps[3].to_string();
|
||||
dependencies.push(Dependency {
|
||||
target_table: table,
|
||||
dependency_type: DependencyType::IndexedAccess { column, index },
|
||||
context: None,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(dependencies)
|
||||
}
|
||||
|
||||
/// Extracts get-var calls as dependencies for transformed scripts.
|
||||
/// These represent self-references to the current table.
|
||||
fn extract_get_var_calls(&self, script: &str, current_table_name: &str) -> Result<Vec<Dependency>, DependencyError> {
|
||||
let mut dependencies = Vec::new();
|
||||
|
||||
// Pattern: (get-var "variable")
|
||||
let get_var_pattern = regex::Regex::new(r#"\(get-var\s+"([^"]+)"\)"#)
|
||||
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
|
||||
|
||||
for caps in get_var_pattern.captures_iter(script) {
|
||||
let variable_name = caps[1].to_string();
|
||||
dependencies.push(Dependency {
|
||||
target_table: current_table_name.to_string(),
|
||||
dependency_type: DependencyType::ColumnAccess { column: variable_name },
|
||||
context: None,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(dependencies)
|
||||
}
|
||||
|
||||
/// Extracts table references from SQL queries in steel_query_sql calls.
|
||||
fn extract_sql_dependencies(&self, script: &str) -> Result<Vec<Dependency>, DependencyError> {
|
||||
let mut dependencies = Vec::new();
|
||||
|
||||
// Pattern: (steel_query_sql "SELECT ... FROM table ...")
|
||||
let sql_pattern = regex::Regex::new(r#"\(\s*steel_query_sql\s+"([^"]+)""#)
|
||||
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
|
||||
|
||||
for caps in sql_pattern.captures_iter(script) {
|
||||
let query = caps[1].to_string();
|
||||
let table_refs = self.extract_table_references_from_sql(&query)?;
|
||||
|
||||
for table in table_refs {
|
||||
dependencies.push(Dependency {
|
||||
target_table: table.clone(),
|
||||
dependency_type: DependencyType::SqlQuery {
|
||||
query_fragment: query.clone()
|
||||
},
|
||||
context: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(dependencies)
|
||||
}
|
||||
|
||||
/// Extracts table names from SQL query text using regex patterns.
|
||||
/// Looks for FROM and JOIN clauses to identify table references.
|
||||
fn extract_table_references_from_sql(&self, sql: &str) -> Result<Vec<String>, DependencyError> {
|
||||
let mut tables = Vec::new();
|
||||
|
||||
// Pattern: FROM table_name or JOIN table_name
|
||||
let table_pattern = regex::Regex::new(r#"(?i)\b(?:FROM|JOIN)\s+(?:"([^"]+)"|(\w+))"#)
|
||||
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
|
||||
|
||||
for caps in table_pattern.captures_iter(sql) {
|
||||
let table = caps.get(1)
|
||||
.or_else(|| caps.get(2))
|
||||
.map(|m| m.as_str().to_string());
|
||||
|
||||
if let Some(table_name) = table {
|
||||
tables.push(table_name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
/// Checks for circular dependencies in the dependency graph.
|
||||
///
|
||||
/// This function validates that adding new dependencies won't create cycles
|
||||
/// that could lead to infinite loops during script execution. Self-references
|
||||
/// are explicitly allowed and filtered out from cycle detection.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `tx` - Database transaction for querying existing dependencies
|
||||
/// * `table_id` - ID of the table adding new dependencies
|
||||
/// * `new_dependencies` - Dependencies to be added
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(())` - No cycles detected, safe to add dependencies
|
||||
/// * `Err(DependencyError)` - Cycle detected or validation failed
|
||||
pub async fn check_for_cycles(
|
||||
&self,
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
table_id: i64,
|
||||
new_dependencies: &[Dependency],
|
||||
) -> Result<(), DependencyError> {
|
||||
// Validate that structured table access respects link constraints
|
||||
self.validate_link_constraints(tx, table_id, new_dependencies).await?;
|
||||
|
||||
// Build current dependency graph excluding self-references
|
||||
let current_deps = sqlx::query!(
|
||||
r#"SELECT sd.source_table_id, sd.target_table_id, st.table_name as source_name, tt.table_name as target_name
|
||||
FROM script_dependencies sd
|
||||
JOIN table_definitions st ON sd.source_table_id = st.id
|
||||
JOIN table_definitions tt ON sd.target_table_id = tt.id
|
||||
WHERE st.schema_id = $1"#,
|
||||
self.schema_id
|
||||
)
|
||||
.fetch_all(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
|
||||
|
||||
let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
|
||||
let mut table_names: HashMap<i64, String> = HashMap::new();
|
||||
|
||||
// Build adjacency list excluding self-references
|
||||
for dep in current_deps {
|
||||
if dep.source_table_id != dep.target_table_id {
|
||||
graph.entry(dep.source_table_id).or_default().push(dep.target_table_id);
|
||||
}
|
||||
table_names.insert(dep.source_table_id, dep.source_name);
|
||||
table_names.insert(dep.target_table_id, dep.target_name);
|
||||
}
|
||||
|
||||
// Add new dependencies to test (excluding self-references)
|
||||
for dep in new_dependencies {
|
||||
let target_id = sqlx::query_scalar!(
|
||||
"SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
|
||||
self.schema_id,
|
||||
dep.target_table
|
||||
)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?
|
||||
.ok_or_else(|| DependencyError::InvalidTableReference {
|
||||
table_name: dep.target_table.clone(),
|
||||
script_context: format!("table_id_{}", table_id),
|
||||
})?;
|
||||
|
||||
// Only add to cycle detection if not a self-reference
|
||||
if table_id != target_id {
|
||||
graph.entry(table_id).or_default().push(target_id);
|
||||
}
|
||||
|
||||
// Ensure table names are available for error reporting
|
||||
if !table_names.contains_key(&table_id) {
|
||||
let source_name = sqlx::query_scalar!(
|
||||
"SELECT table_name FROM table_definitions WHERE id = $1",
|
||||
table_id
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
|
||||
|
||||
table_names.insert(table_id, source_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Detect cycles using DFS algorithm
|
||||
self.detect_cycles_dfs(&graph, &table_names, table_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs depth-first search to detect cycles in the dependency graph.
|
||||
fn dfs_visit(
|
||||
&self,
|
||||
node: i64,
|
||||
states: &mut HashMap<i64, NodeState>,
|
||||
graph: &HashMap<i64, Vec<i64>>,
|
||||
path: &mut Vec<i64>,
|
||||
table_names: &HashMap<i64, String>,
|
||||
starting_table: i64,
|
||||
) -> Result<(), DependencyError> {
|
||||
states.insert(node, NodeState::Visiting);
|
||||
path.push(node);
|
||||
|
||||
if let Some(neighbors) = graph.get(&node) {
|
||||
for &neighbor in neighbors {
|
||||
if !states.contains_key(&neighbor) {
|
||||
states.insert(neighbor, NodeState::Unvisited);
|
||||
}
|
||||
|
||||
match states.get(&neighbor).copied().unwrap_or(NodeState::Unvisited) {
|
||||
NodeState::Visiting => {
|
||||
// Skip self-references as they're allowed
|
||||
if neighbor == node {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found a cycle - build the cycle path
|
||||
let cycle_start_idx = path.iter().position(|&x| x == neighbor).unwrap_or(0);
|
||||
let cycle_path: Vec<String> = path[cycle_start_idx..]
|
||||
.iter()
|
||||
.chain(std::iter::once(&neighbor))
|
||||
.map(|&id| table_names.get(&id).cloned().unwrap_or_else(|| id.to_string()))
|
||||
.collect();
|
||||
|
||||
// Only report as error if cycle involves multiple tables
|
||||
if cycle_path.len() > 2 || (cycle_path.len() == 2 && cycle_path[0] != cycle_path[1]) {
|
||||
let involving_script = table_names.get(&starting_table)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| starting_table.to_string());
|
||||
|
||||
return Err(DependencyError::CircularDependency {
|
||||
cycle_path,
|
||||
involving_script,
|
||||
});
|
||||
}
|
||||
}
|
||||
NodeState::Unvisited => {
|
||||
self.dfs_visit(neighbor, states, graph, path, table_names, starting_table)?;
|
||||
}
|
||||
NodeState::Visited => {
|
||||
// Already processed, no cycle through this path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path.pop();
|
||||
states.insert(node, NodeState::Visited);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates that structured table access respects table link constraints.
|
||||
///
|
||||
/// # Access Rules
|
||||
/// - Self-references are always allowed (table can access its own columns)
|
||||
/// - Structured access (steel_get_column functions) requires explicit table links
|
||||
/// - Raw SQL access (steel_query_sql) is unrestricted
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `tx` - Database transaction for querying table links
|
||||
/// * `source_table_id` - ID of the table with the script
|
||||
/// * `dependencies` - Dependencies to validate
|
||||
async fn validate_link_constraints(
|
||||
&self,
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
source_table_id: i64,
|
||||
dependencies: &[Dependency],
|
||||
) -> Result<(), DependencyError> {
|
||||
let current_table_name = sqlx::query_scalar!(
|
||||
"SELECT table_name FROM table_definitions WHERE id = $1",
|
||||
source_table_id
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
|
||||
|
||||
// Get all valid linked tables for the source table
|
||||
let linked_tables = sqlx::query!(
|
||||
r#"SELECT td.table_name, tdl.is_required
|
||||
FROM table_definition_links tdl
|
||||
JOIN table_definitions td ON tdl.linked_table_id = td.id
|
||||
WHERE tdl.source_table_id = $1"#,
|
||||
source_table_id
|
||||
)
|
||||
.fetch_all(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
|
||||
|
||||
let mut allowed_tables: std::collections::HashSet<String> = linked_tables
|
||||
.into_iter()
|
||||
.map(|row| row.table_name)
|
||||
.collect();
|
||||
|
||||
// Self-references are always allowed
|
||||
allowed_tables.insert(current_table_name.clone());
|
||||
|
||||
// Validate each dependency
|
||||
for dep in dependencies {
|
||||
match &dep.dependency_type {
|
||||
DependencyType::ColumnAccess { column } | DependencyType::IndexedAccess { column, .. } => {
|
||||
// Allow self-references
|
||||
if dep.target_table == current_table_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if table is linked
|
||||
if !allowed_tables.contains(&dep.target_table) {
|
||||
return Err(DependencyError::InvalidTableReference {
|
||||
table_name: dep.target_table.clone(),
|
||||
script_context: format!(
|
||||
"Table '{}' is not linked to '{}'. Add a link in the table definition to access '{}' via steel_get_column functions. Column attempted: '{}'. Note: Self-references are always allowed.",
|
||||
dep.target_table,
|
||||
current_table_name,
|
||||
dep.target_table,
|
||||
column
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
DependencyType::SqlQuery { .. } => {
|
||||
// Raw SQL access is unrestricted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs DFS-based cycle detection on the dependency graph.
|
||||
fn detect_cycles_dfs(
|
||||
&self,
|
||||
graph: &HashMap<i64, Vec<i64>>,
|
||||
table_names: &HashMap<i64, String>,
|
||||
starting_table: i64,
|
||||
) -> Result<(), DependencyError> {
|
||||
let mut states: HashMap<i64, NodeState> = HashMap::new();
|
||||
|
||||
// Initialize all nodes as unvisited
|
||||
for &node in graph.keys() {
|
||||
states.insert(node, NodeState::Unvisited);
|
||||
}
|
||||
|
||||
// Run DFS from each unvisited node
|
||||
for &node in graph.keys() {
|
||||
if states.get(&node) == Some(&NodeState::Unvisited) {
|
||||
let mut path = Vec::new();
|
||||
if let Err(cycle_error) = self.dfs_visit(
|
||||
node,
|
||||
&mut states,
|
||||
graph,
|
||||
&mut path,
|
||||
table_names,
|
||||
starting_table
|
||||
) {
|
||||
return Err(cycle_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Saves dependencies to the database within an existing transaction.
|
||||
///
|
||||
/// This function replaces all existing dependencies for a script with the new set,
|
||||
/// ensuring the database reflects the current script analysis results.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `tx` - Database transaction for atomic updates
|
||||
/// * `script_id` - ID of the script these dependencies belong to
|
||||
/// * `table_id` - ID of the table containing the script
|
||||
/// * `dependencies` - Dependencies to save
|
||||
pub async fn save_dependencies(
|
||||
&self,
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
script_id: i64,
|
||||
table_id: i64,
|
||||
dependencies: &[Dependency],
|
||||
) -> Result<(), DependencyError> {
|
||||
// Clear existing dependencies for this script
|
||||
sqlx::query!("DELETE FROM script_dependencies WHERE script_id = $1", script_id)
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
|
||||
|
||||
// Insert new dependencies
|
||||
for dep in dependencies {
|
||||
let target_id = sqlx::query_scalar!(
|
||||
"SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
|
||||
self.schema_id,
|
||||
dep.target_table
|
||||
)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?
|
||||
.ok_or_else(|| DependencyError::InvalidTableReference {
|
||||
table_name: dep.target_table.clone(),
|
||||
script_context: format!("script_id_{}", script_id),
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO script_dependencies
|
||||
(script_id, source_table_id, target_table_id, dependency_type, context_info)
|
||||
VALUES ($1, $2, $3, $4, $5)"#,
|
||||
script_id,
|
||||
table_id,
|
||||
target_id,
|
||||
dep.dependency_type.as_str(),
|
||||
dep.dependency_type.context_json()
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
229
server/src/table_script/handlers/dependency_utils.rs
Normal file
229
server/src/table_script/handlers/dependency_utils.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
// src/table_script/handlers/dependency_utils.rs
|
||||
// Utility functions for dependency analysis and debugging
|
||||
|
||||
use sqlx::PgPool;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Get a visual representation of the dependency graph for a schema
|
||||
pub async fn get_dependency_graph_visualization(
|
||||
db_pool: &PgPool,
|
||||
schema_id: i64,
|
||||
) -> Result<Value, sqlx::Error> {
|
||||
let dependencies = sqlx::query!(
|
||||
r#"SELECT
|
||||
st.table_name as source_table,
|
||||
tt.table_name as target_table,
|
||||
sd.dependency_type,
|
||||
sd.context_info,
|
||||
COUNT(*) as dependency_count
|
||||
FROM script_dependencies sd
|
||||
JOIN table_definitions st ON sd.source_table_id = st.id
|
||||
JOIN table_definitions tt ON sd.target_table_id = tt.id
|
||||
WHERE st.schema_id = $1
|
||||
GROUP BY st.table_name, tt.table_name, sd.dependency_type, sd.context_info
|
||||
ORDER BY st.table_name, tt.table_name"#,
|
||||
schema_id
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await?;
|
||||
|
||||
let mut graph = HashMap::new();
|
||||
for dep in dependencies {
|
||||
let source_entry = graph.entry(dep.source_table.clone()).or_insert_with(|| json!({
|
||||
"table": dep.source_table,
|
||||
"dependencies": []
|
||||
}));
|
||||
|
||||
if let Some(deps_array) = source_entry.get_mut("dependencies") {
|
||||
if let Some(deps) = deps_array.as_array_mut() {
|
||||
deps.push(json!({
|
||||
"target": dep.target_table,
|
||||
"type": dep.dependency_type,
|
||||
"context": dep.context_info,
|
||||
"count": dep.dependency_count.unwrap_or(0)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"schema_id": schema_id,
|
||||
"dependency_graph": graph.into_values().collect::<Vec<_>>()
|
||||
}))
|
||||
}
|
||||
|
||||
/// Check if a schema has any circular dependencies
|
||||
pub async fn check_schema_for_cycles(
|
||||
db_pool: &PgPool,
|
||||
schema_id: i64,
|
||||
) -> Result<Vec<String>, sqlx::Error> {
|
||||
// This is a simplified cycle detection for monitoring purposes
|
||||
let dependencies = sqlx::query!(
|
||||
r#"SELECT sd.source_table_id, sd.target_table_id, st.table_name as source_table, tt.table_name as target_table
|
||||
FROM script_dependencies sd
|
||||
JOIN table_definitions st ON sd.source_table_id = st.id
|
||||
JOIN table_definitions tt ON sd.target_table_id = tt.id
|
||||
WHERE st.schema_id = $1"#,
|
||||
schema_id
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await?;
|
||||
|
||||
let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
|
||||
let mut table_names: HashMap<i64, String> = HashMap::new();
|
||||
|
||||
for dep in dependencies {
|
||||
graph.entry(dep.source_table_id).or_default().push(dep.target_table_id);
|
||||
table_names.insert(dep.source_table_id, dep.source_table);
|
||||
table_names.insert(dep.target_table_id, dep.target_table);
|
||||
}
|
||||
|
||||
// Simple cycle detection using DFS
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
let mut rec_stack = std::collections::HashSet::new();
|
||||
let mut cycles = Vec::new();
|
||||
|
||||
for &node in graph.keys() {
|
||||
if !visited.contains(&node) {
|
||||
if let Some(cycle) = detect_cycle_dfs(
|
||||
node,
|
||||
&graph,
|
||||
&mut visited,
|
||||
&mut rec_stack,
|
||||
&table_names
|
||||
) {
|
||||
cycles.push(cycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cycles)
|
||||
}
|
||||
|
||||
fn detect_cycle_dfs(
|
||||
node: i64,
|
||||
graph: &HashMap<i64, Vec<i64>>,
|
||||
visited: &mut std::collections::HashSet<i64>,
|
||||
rec_stack: &mut std::collections::HashSet<i64>,
|
||||
table_names: &HashMap<i64, String>,
|
||||
) -> Option<String> {
|
||||
visited.insert(node);
|
||||
rec_stack.insert(node);
|
||||
|
||||
if let Some(neighbors) = graph.get(&node) {
|
||||
for &neighbor in neighbors {
|
||||
if !visited.contains(&neighbor) {
|
||||
if let Some(cycle) = detect_cycle_dfs(neighbor, graph, visited, rec_stack, table_names) {
|
||||
return Some(cycle);
|
||||
}
|
||||
} else if rec_stack.contains(&neighbor) {
|
||||
// Found a cycle
|
||||
let node_name = table_names.get(&node).cloned().unwrap_or_else(|| node.to_string());
|
||||
let neighbor_name = table_names.get(&neighbor).cloned().unwrap_or_else(|| neighbor.to_string());
|
||||
return Some(format!("{} -> {}", node_name, neighbor_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rec_stack.remove(&node);
|
||||
None
|
||||
}
|
||||
|
||||
/// Get processing order for tables based on dependencies
|
||||
/// Tables with no dependencies come first, then tables that depend only on already-processed tables
|
||||
pub async fn get_table_processing_order(
|
||||
db_pool: &PgPool,
|
||||
schema_id: i64,
|
||||
) -> Result<Vec<String>, sqlx::Error> {
|
||||
let all_tables = sqlx::query!(
|
||||
"SELECT id, table_name FROM table_definitions WHERE schema_id = $1 ORDER BY table_name",
|
||||
schema_id
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await?;
|
||||
|
||||
let dependencies = sqlx::query!(
|
||||
r#"SELECT source_table_id, target_table_id
|
||||
FROM script_dependencies sd
|
||||
JOIN table_definitions td ON sd.source_table_id = td.id
|
||||
WHERE td.schema_id = $1"#,
|
||||
schema_id
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await?;
|
||||
|
||||
// Build dependency graph
|
||||
let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
|
||||
let mut in_degree: HashMap<i64, usize> = HashMap::new();
|
||||
let mut table_names: HashMap<i64, String> = HashMap::new();
|
||||
|
||||
// Initialize all tables
|
||||
for table in &all_tables {
|
||||
in_degree.insert(table.id, 0);
|
||||
table_names.insert(table.id, table.table_name.clone());
|
||||
graph.insert(table.id, Vec::new());
|
||||
}
|
||||
|
||||
// Build graph and calculate in-degrees
|
||||
for dep in dependencies {
|
||||
graph.entry(dep.target_table_id).or_default().push(dep.source_table_id);
|
||||
*in_degree.entry(dep.source_table_id).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
// Topological sort using Kahn's algorithm
|
||||
let mut queue: Vec<i64> = in_degree
|
||||
.iter()
|
||||
.filter(|(_, °ree)| degree == 0)
|
||||
.map(|(&id, _)| id)
|
||||
.collect();
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
while let Some(node) = queue.pop() {
|
||||
result.push(table_names[&node].clone());
|
||||
|
||||
if let Some(neighbors) = graph.get(&node) {
|
||||
for &neighbor in neighbors {
|
||||
if let Some(degree) = in_degree.get_mut(&neighbor) {
|
||||
*degree -= 1;
|
||||
if *degree == 0 {
|
||||
queue.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If result doesn't contain all tables, there are cycles
|
||||
if result.len() != all_tables.len() {
|
||||
return Err(sqlx::Error::Protocol("Circular dependencies detected - cannot determine processing order".into()));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cycle_detection_simple() {
|
||||
// Test the cycle detection logic with a simple case
|
||||
let mut graph = HashMap::new();
|
||||
graph.insert(1, vec![2]);
|
||||
graph.insert(2, vec![1]); // 1 -> 2 -> 1 (cycle)
|
||||
|
||||
let mut table_names = HashMap::new();
|
||||
table_names.insert(1, "table_a".to_string());
|
||||
table_names.insert(2, "table_b".to_string());
|
||||
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
let mut rec_stack = std::collections::HashSet::new();
|
||||
|
||||
let cycle = detect_cycle_dfs(1, &graph, &mut visited, &mut rec_stack, &table_names);
|
||||
assert!(cycle.is_some());
|
||||
let cycle_str = cycle.unwrap();
|
||||
assert!(cycle_str.contains("table_a") && cycle_str.contains("table_b"));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,357 @@
|
||||
// src/table_script/handlers/post_table_script.rs
|
||||
// TODO MAKE THE SCRIPTS PUSH ONLY TO THE EMPTY FILES
|
||||
|
||||
use tonic::Status;
|
||||
use sqlx::{PgPool, Error as SqlxError};
|
||||
use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse};
|
||||
use common::proto::komp_ac::table_script::{PostTableScriptRequest, TableScriptResponse};
|
||||
use serde_json::Value;
|
||||
use crate::steel::server::syntax_parser::SyntaxParser;
|
||||
use steel_decimal::SteelDecimal;
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::table_script::handlers::dependency_analyzer::DependencyAnalyzer;
|
||||
|
||||
const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "created_at"];
|
||||
|
||||
// TODO MAKE SCRIPT PUSH ONLY TO THE EMPTY TABLES
|
||||
/// Validates the target column and ensures it is not a system column.
|
||||
// Define prohibited data types for Steel scripts (boolean is explicitly allowed)
|
||||
const PROHIBITED_TYPES: &[&str] = &["BIGINT", "DATE", "TIMESTAMPTZ"];
|
||||
const MATH_PROHIBITED_TYPES: &[&str] = &["BIGINT", "TEXT", "BOOLEAN", "DATE", "TIMESTAMPTZ"];
|
||||
|
||||
// Math operations that Steel Decimal will transform
|
||||
const MATH_OPERATIONS: &[&str] = &[
|
||||
"+", "-", "*", "/", "^", "**", "pow", "sqrt",
|
||||
">", "<", "=", ">=", "<=", "min", "max", "abs",
|
||||
"round", "ln", "log", "log10", "exp", "sin", "cos", "tan"
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum SExpr {
|
||||
Atom(String),
|
||||
List(Vec<SExpr>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Parser {
|
||||
tokens: Vec<String>,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn new(script: &str) -> Self {
|
||||
let tokens = Self::tokenize(script);
|
||||
Self { tokens, position: 0 }
|
||||
}
|
||||
|
||||
fn tokenize(script: &str) -> Vec<String> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut current_token = String::new();
|
||||
let mut in_string = false;
|
||||
let mut escape_next = false;
|
||||
|
||||
for ch in script.chars() {
|
||||
if escape_next {
|
||||
current_token.push(ch);
|
||||
escape_next = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
match ch {
|
||||
'\\' if in_string => {
|
||||
escape_next = true;
|
||||
current_token.push(ch);
|
||||
}
|
||||
'"' => {
|
||||
current_token.push(ch);
|
||||
if in_string {
|
||||
// End of string - push the complete string token
|
||||
tokens.push(current_token.clone());
|
||||
current_token.clear();
|
||||
}
|
||||
in_string = !in_string;
|
||||
}
|
||||
'(' | ')' if !in_string => {
|
||||
if !current_token.is_empty() {
|
||||
tokens.push(current_token.clone());
|
||||
current_token.clear();
|
||||
}
|
||||
tokens.push(ch.to_string());
|
||||
}
|
||||
ch if ch.is_whitespace() && !in_string => {
|
||||
if !current_token.is_empty() {
|
||||
tokens.push(current_token.clone());
|
||||
current_token.clear();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
current_token.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !current_token.is_empty() {
|
||||
tokens.push(current_token);
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
fn parse(&mut self) -> Result<Vec<SExpr>, String> {
|
||||
let mut expressions = Vec::new();
|
||||
|
||||
while self.position < self.tokens.len() {
|
||||
expressions.push(self.parse_expr()?);
|
||||
}
|
||||
|
||||
Ok(expressions)
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self) -> Result<SExpr, String> {
|
||||
if self.position >= self.tokens.len() {
|
||||
return Err("Unexpected end of input".to_string());
|
||||
}
|
||||
|
||||
let token = &self.tokens[self.position];
|
||||
|
||||
if token == "(" {
|
||||
self.position += 1; // consume '('
|
||||
let mut elements = Vec::new();
|
||||
|
||||
while self.position < self.tokens.len() && self.tokens[self.position] != ")" {
|
||||
elements.push(self.parse_expr()?);
|
||||
}
|
||||
|
||||
if self.position >= self.tokens.len() {
|
||||
return Err("Missing closing parenthesis".to_string());
|
||||
}
|
||||
|
||||
self.position += 1; // consume ')'
|
||||
Ok(SExpr::List(elements))
|
||||
} else {
|
||||
self.position += 1;
|
||||
Ok(SExpr::Atom(token.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MathValidator {
|
||||
column_references: Vec<(String, String)>, // (table, column) pairs found in math contexts
|
||||
}
|
||||
|
||||
impl MathValidator {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
column_references: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_expressions(&mut self, expressions: &[SExpr]) -> Result<(), String> {
|
||||
for expr in expressions {
|
||||
self.check_expression(expr, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_expression(&mut self, expr: &SExpr, in_math_context: bool) -> Result<(), String> {
|
||||
match expr {
|
||||
SExpr::Atom(_) => Ok(()),
|
||||
SExpr::List(elements) => {
|
||||
if elements.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Check if this is a math operation
|
||||
let is_math = if let SExpr::Atom(op) = &elements[0] {
|
||||
MATH_OPERATIONS.contains(&op.as_str())
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Check if this is a column access function
|
||||
if let SExpr::Atom(func) = &elements[0] {
|
||||
if func == "steel_get_column" && in_math_context {
|
||||
self.extract_column_reference_from_steel_get_column(elements)?;
|
||||
} else if func == "steel_get_column_with_index" && in_math_context {
|
||||
self.extract_column_reference_from_steel_get_column_with_index(elements)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check all elements, marking math context appropriately
|
||||
for element in &elements[1..] { // Skip the operator/function name
|
||||
self.check_expression(element, in_math_context || is_math)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_column_reference_from_steel_get_column(&mut self, elements: &[SExpr]) -> Result<(), String> {
|
||||
// (steel_get_column "table" "column")
|
||||
if elements.len() >= 3 {
|
||||
if let (SExpr::Atom(table), SExpr::Atom(column)) = (&elements[1], &elements[2]) {
|
||||
let table_name = self.unquote_string(table)?;
|
||||
let column_name = self.unquote_string(column)?;
|
||||
self.column_references.push((table_name, column_name));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_column_reference_from_steel_get_column_with_index(&mut self, elements: &[SExpr]) -> Result<(), String> {
|
||||
// (steel_get_column_with_index "table" index "column")
|
||||
if elements.len() >= 4 {
|
||||
if let (SExpr::Atom(table), SExpr::Atom(column)) = (&elements[1], &elements[3]) {
|
||||
let table_name = self.unquote_string(table)?;
|
||||
let column_name = self.unquote_string(column)?;
|
||||
self.column_references.push((table_name, column_name));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unquote_string(&self, s: &str) -> Result<String, String> {
|
||||
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
|
||||
Ok(s[1..s.len()-1].to_string())
|
||||
} else {
|
||||
Err(format!("Expected quoted string, got: {}", s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Valide script is not empty
|
||||
fn validate_script_basic_syntax(script: &str) -> Result<(), Status> {
|
||||
let trimmed = script.trim();
|
||||
|
||||
// Check for empty script
|
||||
if trimmed.is_empty() {
|
||||
return Err(Status::invalid_argument("Script cannot be empty"));
|
||||
}
|
||||
|
||||
// Basic parentheses balance check
|
||||
let mut paren_count = 0;
|
||||
for ch in trimmed.chars() {
|
||||
match ch {
|
||||
'(' => paren_count += 1,
|
||||
')' => {
|
||||
paren_count -= 1;
|
||||
if paren_count < 0 {
|
||||
return Err(Status::invalid_argument("Unbalanced parentheses: closing ')' without matching opening '('"));
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if paren_count != 0 {
|
||||
return Err(Status::invalid_argument("Unbalanced parentheses: missing closing parentheses"));
|
||||
}
|
||||
|
||||
// Check for basic S-expression structure
|
||||
if !trimmed.starts_with('(') {
|
||||
return Err(Status::invalid_argument("Script must start with an opening parenthesis '('"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse Steel script and extract column references used in mathematical contexts
|
||||
fn extract_math_column_references(script: &str) -> Result<Vec<(String, String)>, String> {
|
||||
let mut parser = Parser::new(script);
|
||||
let expressions = parser.parse()
|
||||
.map_err(|e| format!("Parse error: {}", e))?;
|
||||
|
||||
let mut validator = MathValidator::new();
|
||||
validator.validate_expressions(&expressions)
|
||||
.map_err(|e| format!("Validation error: {}", e))?;
|
||||
|
||||
Ok(validator.column_references)
|
||||
}
|
||||
|
||||
/// Validate that mathematical operations don't use TEXT or BOOLEAN columns
|
||||
async fn validate_math_operations_column_types(
|
||||
db_pool: &PgPool,
|
||||
schema_id: i64,
|
||||
script: &str,
|
||||
) -> Result<(), Status> {
|
||||
// Extract column references from mathematical contexts using proper S-expression parsing
|
||||
let column_refs = extract_math_column_references(script)
|
||||
.map_err(|e| Status::invalid_argument(format!("Script parsing failed: {}", e)))?;
|
||||
|
||||
if column_refs.is_empty() {
|
||||
return Ok(()); // No column references in math operations
|
||||
}
|
||||
|
||||
// Get all unique table names referenced in math operations
|
||||
let table_names: HashSet<String> = column_refs.iter()
|
||||
.map(|(table, _)| table.clone())
|
||||
.collect();
|
||||
|
||||
// Fetch table definitions for all referenced tables
|
||||
let table_definitions = sqlx::query!(
|
||||
r#"SELECT table_name, columns FROM table_definitions
|
||||
WHERE schema_id = $1 AND table_name = ANY($2)"#,
|
||||
schema_id,
|
||||
&table_names.into_iter().collect::<Vec<_>>()
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Failed to fetch table definitions: {}", e)))?;
|
||||
|
||||
// Build a map of table_name -> column_name -> column_type
|
||||
let mut table_column_types: HashMap<String, HashMap<String, String>> = HashMap::new();
|
||||
|
||||
for table_def in table_definitions {
|
||||
let columns: Vec<String> = serde_json::from_value(table_def.columns)
|
||||
.map_err(|e| Status::internal(format!("Invalid column data for table '{}': {}", table_def.table_name, e)))?;
|
||||
|
||||
let mut column_types = HashMap::new();
|
||||
for column_def in columns {
|
||||
let mut parts = column_def.split_whitespace();
|
||||
if let (Some(name), Some(data_type)) = (parts.next(), parts.next()) {
|
||||
let column_name = name.trim_matches('"');
|
||||
column_types.insert(column_name.to_string(), data_type.to_string());
|
||||
}
|
||||
}
|
||||
table_column_types.insert(table_def.table_name, column_types);
|
||||
}
|
||||
|
||||
// Check each column reference in mathematical operations
|
||||
for (table_name, column_name) in column_refs {
|
||||
if let Some(table_columns) = table_column_types.get(&table_name) {
|
||||
if let Some(column_type) = table_columns.get(&column_name) {
|
||||
let normalized_type = normalize_data_type(column_type);
|
||||
|
||||
// Check if this type is prohibited in math operations
|
||||
if MATH_PROHIBITED_TYPES.iter().any(|&prohibited| normalized_type.starts_with(prohibited)) {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Cannot use column '{}' of type '{}' from table '{}' in mathematical operations. Mathematical operations cannot use columns of type: {}",
|
||||
column_name,
|
||||
column_type,
|
||||
table_name,
|
||||
MATH_PROHIBITED_TYPES.join(", ")
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Script references column '{}' in table '{}' but this column does not exist",
|
||||
column_name,
|
||||
table_name
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Script references table '{}' in mathematical operations but this table does not exist in this schema",
|
||||
table_name
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the target column and ensures it is not a system column or prohibited type.
|
||||
/// Returns the column type if valid.
|
||||
fn validate_target_column(
|
||||
table_name: &str,
|
||||
@@ -35,32 +378,237 @@ fn validate_target_column(
|
||||
.collect();
|
||||
|
||||
// Find the target column and return its type
|
||||
column_info
|
||||
let column_type = column_info
|
||||
.iter()
|
||||
.find(|(name, _)| *name == target)
|
||||
.map(|(_, dt)| dt.to_string())
|
||||
.ok_or_else(|| format!("Target column '{}' not defined in table '{}'", target, table_name))
|
||||
.ok_or_else(|| format!("Target column '{}' not defined in table '{}'", target, table_name))?;
|
||||
|
||||
// Check if the target column type is prohibited
|
||||
if is_prohibited_type(&column_type) {
|
||||
return Err(format!(
|
||||
"Cannot create script for column '{}' with type '{}'. Steel scripts cannot target columns of type: {}",
|
||||
target,
|
||||
column_type,
|
||||
PROHIBITED_TYPES.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
// Add helpful info for boolean columns
|
||||
let normalized_type = normalize_data_type(&column_type);
|
||||
if normalized_type == "BOOLEAN" || normalized_type == "BOOL" {
|
||||
println!("Info: Target column '{}' is boolean type. Values will be converted to Steel format (#true/#false)", target);
|
||||
}
|
||||
|
||||
Ok(column_type)
|
||||
}
|
||||
|
||||
/// Handles the creation of a new table script.
|
||||
/// Check if a data type is prohibited for Steel scripts
|
||||
/// Note: BOOLEAN/BOOL is explicitly allowed and handled with special conversion
|
||||
fn is_prohibited_type(data_type: &str) -> bool {
|
||||
let normalized_type = normalize_data_type(data_type);
|
||||
PROHIBITED_TYPES.iter().any(|&prohibited| normalized_type.starts_with(prohibited))
|
||||
}
|
||||
|
||||
/// Normalize data type for comparison (handle NUMERIC variations, etc.)
|
||||
fn normalize_data_type(data_type: &str) -> String {
|
||||
data_type.to_uppercase()
|
||||
.split('(') // Remove precision/scale from NUMERIC(x,y)
|
||||
.next()
|
||||
.unwrap_or(data_type)
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Parse Steel script to extract all table/column references
|
||||
fn extract_column_references_from_script(script: &str) -> Vec<(String, String)> {
|
||||
let mut references = Vec::new();
|
||||
|
||||
// Regex patterns to match Steel function calls
|
||||
let patterns = [
|
||||
// (steel_get_column "table_name" "column_name")
|
||||
r#"\(steel_get_column\s+"([^"]+)"\s+"([^"]+)"\)"#,
|
||||
// (steel_get_column_with_index "table_name" index "column_name")
|
||||
r#"\(steel_get_column_with_index\s+"([^"]+)"\s+\d+\s+"([^"]+)"\)"#,
|
||||
];
|
||||
|
||||
for pattern in &patterns {
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
for cap in re.captures_iter(script) {
|
||||
if let (Some(table), Some(column)) = (cap.get(1), cap.get(2)) {
|
||||
references.push((table.as_str().to_string(), column.as_str().to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for steel_get_column_with_index pattern (table, column are in different positions)
|
||||
if let Ok(re) = Regex::new(r#"\(steel_get_column_with_index\s+"([^"]+)"\s+\d+\s+"([^"]+)"\)"#) {
|
||||
for cap in re.captures_iter(script) {
|
||||
if let (Some(table), Some(column)) = (cap.get(1), cap.get(2)) {
|
||||
references.push((table.as_str().to_string(), column.as_str().to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
references
|
||||
}
|
||||
|
||||
/// Validate that script doesn't reference prohibited column types by checking actual DB schema
|
||||
async fn validate_script_column_references(
|
||||
db_pool: &PgPool,
|
||||
schema_id: i64,
|
||||
script: &str,
|
||||
) -> Result<(), Status> {
|
||||
// Extract all table/column references from the script
|
||||
let references = extract_column_references_from_script(script);
|
||||
|
||||
if references.is_empty() {
|
||||
return Ok(()); // No column references to validate
|
||||
}
|
||||
|
||||
// Get all unique table names referenced in the script
|
||||
let table_names: HashSet<String> = references.iter()
|
||||
.map(|(table, _)| table.clone())
|
||||
.collect();
|
||||
|
||||
// Fetch table definitions for all referenced tables
|
||||
for table_name in table_names {
|
||||
// Query the actual table definition from the database
|
||||
let table_def = sqlx::query!(
|
||||
r#"SELECT table_name, columns FROM table_definitions
|
||||
WHERE schema_id = $1 AND table_name = $2"#,
|
||||
schema_id,
|
||||
table_name
|
||||
)
|
||||
.fetch_optional(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Failed to fetch table definition for '{}': {}", table_name, e)))?;
|
||||
|
||||
if let Some(table_def) = table_def {
|
||||
// Check each column reference for this table
|
||||
for (ref_table, ref_column) in &references {
|
||||
if ref_table == &table_name {
|
||||
// Validate this specific column reference
|
||||
if let Err(error_msg) = validate_referenced_column_type(&table_name, ref_column, &table_def.columns) {
|
||||
return Err(Status::invalid_argument(error_msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Script references table '{}' which does not exist in this schema",
|
||||
table_name
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate that a referenced column doesn't have a prohibited type
|
||||
fn validate_referenced_column_type(table_name: &str, column_name: &str, table_columns: &Value) -> Result<(), String> {
|
||||
// Parse the columns JSON into a vector of strings
|
||||
let columns: Vec<String> = serde_json::from_value(table_columns.clone())
|
||||
.map_err(|e| format!("Invalid column data for table '{}': {}", table_name, e))?;
|
||||
|
||||
// Extract column names and types
|
||||
let column_info: Vec<(&str, &str)> = columns
|
||||
.iter()
|
||||
.filter_map(|c| {
|
||||
let mut parts = c.split_whitespace();
|
||||
let name = parts.next()?.trim_matches('"');
|
||||
let data_type = parts.next()?;
|
||||
Some((name, data_type))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Find the referenced column and check its type
|
||||
if let Some((_, column_type)) = column_info.iter().find(|(name, _)| *name == column_name) {
|
||||
if is_prohibited_type(column_type) {
|
||||
return Err(format!(
|
||||
"Script references column '{}' in table '{}' which has prohibited type '{}'. Steel scripts cannot access columns of type: {}",
|
||||
column_name,
|
||||
table_name,
|
||||
column_type,
|
||||
PROHIBITED_TYPES.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
// Log info for boolean columns
|
||||
let normalized_type = normalize_data_type(column_type);
|
||||
if normalized_type == "BOOLEAN" || normalized_type == "BOOL" {
|
||||
println!("Info: Script references boolean column '{}' in table '{}'. Values will be converted to Steel format (#true/#false)", column_name, table_name);
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Script references column '{}' in table '{}' but this column does not exist",
|
||||
column_name,
|
||||
table_name
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse Steel SQL queries to check for prohibited type usage (basic heuristic)
|
||||
fn validate_sql_queries_in_script(script: &str) -> Result<(), String> {
|
||||
// Look for steel_query_sql calls
|
||||
if let Ok(re) = Regex::new(r#"\(steel_query_sql\s+"([^"]+)"\)"#) {
|
||||
for cap in re.captures_iter(script) {
|
||||
if let Some(query) = cap.get(1) {
|
||||
let sql = query.as_str().to_uppercase();
|
||||
|
||||
// Basic heuristic checks for prohibited type operations
|
||||
let prohibited_patterns = [
|
||||
"EXTRACT(",
|
||||
"DATE_PART(",
|
||||
"::DATE",
|
||||
"::TIMESTAMPTZ",
|
||||
"::BIGINT",
|
||||
"CAST(", // Could be casting to prohibited types
|
||||
];
|
||||
|
||||
for pattern in &prohibited_patterns {
|
||||
if sql.contains(pattern) {
|
||||
return Err(format!(
|
||||
"Script contains SQL query with potentially prohibited type operations: '{}'. Steel scripts cannot use operations on types: {}",
|
||||
query.as_str(),
|
||||
PROHIBITED_TYPES.join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles the creation of a new table script with dependency validation.
|
||||
pub async fn post_table_script(
|
||||
db_pool: &PgPool,
|
||||
request: PostTableScriptRequest,
|
||||
) -> Result<TableScriptResponse, Status> {
|
||||
// Basic script validation first
|
||||
validate_script_basic_syntax(&request.script)?;
|
||||
|
||||
// Start a transaction for ALL operations - critical for atomicity
|
||||
let mut tx = db_pool.begin().await
|
||||
.map_err(|e| Status::internal(format!("Failed to start transaction: {}", e)))?;
|
||||
|
||||
// Fetch the table definition
|
||||
let table_def = sqlx::query!(
|
||||
r#"SELECT id, table_name, columns, schema_id
|
||||
FROM table_definitions WHERE id = $1"#,
|
||||
request.table_definition_id
|
||||
)
|
||||
.fetch_optional(db_pool)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Status::internal(format!("Failed to fetch table definition: {}", e))
|
||||
})?
|
||||
.map_err(|e| Status::internal(format!("Failed to fetch table definition: {}", e)))?
|
||||
.ok_or_else(|| Status::not_found("Table definition not found"))?;
|
||||
|
||||
// Validate the target column and get its type
|
||||
// Validate the target column and get its type (includes prohibited type check)
|
||||
let column_type = validate_target_column(
|
||||
&table_def.table_name,
|
||||
&request.target_column,
|
||||
@@ -68,16 +616,46 @@ pub async fn post_table_script(
|
||||
)
|
||||
.map_err(|e| Status::invalid_argument(e))?;
|
||||
|
||||
// Parse and transform the script using the syntax parser
|
||||
let parser = SyntaxParser::new();
|
||||
let parsed_script = parser.parse(&request.script, &table_def.table_name);
|
||||
// REORDER: Math validation FIRST so we get specific error messages for math operations
|
||||
validate_math_operations_column_types(db_pool, table_def.schema_id, &request.script).await?;
|
||||
|
||||
// Insert the script into the database
|
||||
// THEN general column validation (catches non-math prohibited access)
|
||||
validate_script_column_references(db_pool, table_def.schema_id, &request.script).await?;
|
||||
|
||||
// Validate SQL queries in script for prohibited type operations
|
||||
validate_sql_queries_in_script(&request.script)
|
||||
.map_err(|e| Status::invalid_argument(e))?;
|
||||
|
||||
// Create dependency analyzer for this schema
|
||||
let analyzer = DependencyAnalyzer::new(table_def.schema_id);
|
||||
|
||||
// Analyze script dependencies
|
||||
let dependencies = analyzer
|
||||
.analyze_script_dependencies(&request.script, &table_def.table_name)
|
||||
.map_err(|e| Status::from(e))?;
|
||||
|
||||
// Check for circular dependencies BEFORE making any changes
|
||||
// Pass the transaction to ensure we see any existing dependencies
|
||||
analyzer
|
||||
.check_for_cycles(&mut tx, table_def.id, &dependencies)
|
||||
.await
|
||||
.map_err(|e| Status::from(e))?;
|
||||
|
||||
// Transform the script using steel_decimal (this happens AFTER validation)
|
||||
let steel_decimal = SteelDecimal::new();
|
||||
let parsed_script = steel_decimal.transform(&request.script);
|
||||
|
||||
// Insert or update the script
|
||||
let script_record = sqlx::query!(
|
||||
r#"INSERT INTO table_scripts
|
||||
(table_definitions_id, target_table, target_column,
|
||||
target_column_type, script, description, schema_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (table_definitions_id, target_column)
|
||||
DO UPDATE SET
|
||||
script = EXCLUDED.script,
|
||||
description = EXCLUDED.description,
|
||||
target_column_type = EXCLUDED.target_column_type
|
||||
RETURNING id"#,
|
||||
request.table_definition_id,
|
||||
table_def.table_name,
|
||||
@@ -87,18 +665,83 @@ pub async fn post_table_script(
|
||||
request.description,
|
||||
table_def.schema_id
|
||||
)
|
||||
.fetch_one(db_pool)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
SqlxError::Database(db_err) if db_err.constraint() == Some("table_scripts_table_definitions_id_target_column_key") => {
|
||||
Status::already_exists("Script already exists for this column")
|
||||
.map_err(|e| {
|
||||
match e {
|
||||
SqlxError::Database(db_err)
|
||||
if db_err.constraint() == Some("table_scripts_table_definitions_id_target_column_key") => {
|
||||
Status::already_exists("Script already exists for this column")
|
||||
}
|
||||
_ => Status::internal(format!("Failed to insert script: {}", e)),
|
||||
}
|
||||
_ => Status::internal(format!("Failed to insert script: {}", e)),
|
||||
})?;
|
||||
|
||||
// Return the response with the new script ID
|
||||
// Save the dependencies within the same transaction
|
||||
analyzer
|
||||
.save_dependencies(&mut tx, script_record.id, table_def.id, &dependencies)
|
||||
.await
|
||||
.map_err(|e| Status::from(e))?;
|
||||
|
||||
// Only now commit the entire transaction - script + dependencies together
|
||||
tx.commit().await
|
||||
.map_err(|e| Status::internal(format!("Failed to commit transaction: {}", e)))?;
|
||||
|
||||
// Generate warnings for potential issues
|
||||
let warnings = generate_warnings(&dependencies, &table_def.table_name);
|
||||
|
||||
Ok(TableScriptResponse {
|
||||
id: script_record.id,
|
||||
warnings: String::new(), // No warnings for now
|
||||
warnings,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate helpful warnings for script dependencies
|
||||
fn generate_warnings(dependencies: &[crate::table_script::handlers::dependency_analyzer::Dependency], table_name: &str) -> String {
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
// Check for self-references
|
||||
if dependencies.iter().any(|d| d.target_table == table_name) {
|
||||
warnings.push("Warning: Script references its own table, which may cause issues during initial population.".to_string());
|
||||
}
|
||||
|
||||
// Check for complex SQL queries
|
||||
let sql_deps_count = dependencies.iter()
|
||||
.filter(|d| matches!(d.dependency_type, crate::table_script::handlers::dependency_analyzer::DependencyType::SqlQuery { .. }))
|
||||
.count();
|
||||
|
||||
if sql_deps_count > 0 {
|
||||
warnings.push(format!(
|
||||
"Warning: Script contains {} raw SQL quer{}, ensure they are read-only and reference valid tables.",
|
||||
sql_deps_count,
|
||||
if sql_deps_count == 1 { "y" } else { "ies" }
|
||||
));
|
||||
}
|
||||
|
||||
// Check for many dependencies
|
||||
if dependencies.len() > 5 {
|
||||
warnings.push(format!(
|
||||
"Warning: Script depends on {} tables, which may affect processing performance.",
|
||||
dependencies.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Count structured access dependencies
|
||||
let structured_deps_count = dependencies.iter()
|
||||
.filter(|d| matches!(
|
||||
d.dependency_type,
|
||||
crate::table_script::handlers::dependency_analyzer::DependencyType::ColumnAccess { .. } |
|
||||
crate::table_script::handlers::dependency_analyzer::DependencyType::IndexedAccess { .. }
|
||||
))
|
||||
.count();
|
||||
|
||||
if structured_deps_count > 0 {
|
||||
warnings.push(format!(
|
||||
"Info: Script uses {} linked table{} via steel_get_column functions.",
|
||||
structured_deps_count,
|
||||
if structured_deps_count == 1 { "" } else { "s" }
|
||||
));
|
||||
}
|
||||
|
||||
warnings.join(" ")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// src/tables_data/mod.rs
|
||||
|
||||
// src/table_script/mod.rs
|
||||
pub mod handlers;
|
||||
|
||||
pub use handlers::*;
|
||||
|
||||
@@ -4,7 +4,7 @@ grpcurl -plaintext \
|
||||
"table_name": "2025_customer"
|
||||
}' \
|
||||
localhost:50051 \
|
||||
multieko2.table_structure.TableStructureService/GetTableStructure
|
||||
komp_ac.table_structure.TableStructureService/GetTableStructure
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/table_structure/handlers/table_structure.rs
|
||||
use common::proto::multieko2::table_structure::{
|
||||
use common::proto::komp_ac::table_structure::{
|
||||
GetTableStructureRequest, TableColumn, TableStructureResponse,
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -2,7 +2,7 @@ grpcurl -plaintext -d '{
|
||||
"profile_name": "default",
|
||||
"table_name": "2025_adresar",
|
||||
"position": 1
|
||||
}' localhost:50051 multieko2.tables_data.TablesData/GetTableDataByPosition
|
||||
}' localhost:50051 komp_ac.tables_data.TablesData/GetTableDataByPosition
|
||||
{
|
||||
"data": {
|
||||
"banka": "New Banka",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Valid get request:
|
||||
|
||||
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 2}' localhost:50051 multieko2.tables_data.TablesData/GetTableData
|
||||
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 2}' localhost:50051 komp_ac.tables_data.TablesData/GetTableData
|
||||
|
||||
{
|
||||
"data": {
|
||||
@@ -26,7 +26,7 @@ grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar",
|
||||
|
||||
Request of a deleted data:
|
||||
|
||||
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 1}' localhost:50051 multieko2.tables_data.TablesData/GetTableData
|
||||
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 1}' localhost:50051 komp_ac.tables_data.TablesData/GetTableData
|
||||
|
||||
ERROR:
|
||||
Code: NotFound
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar"}' localhost:50051 multieko2.tables_data.TablesData/GetTableDataCount
|
||||
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar"}' localhost:50051 komp_ac.tables_data.TablesData/GetTableDataCount
|
||||
|
||||
{
|
||||
"count": "1"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
|
||||
❯ grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
@@ -26,7 +26,7 @@
|
||||
"support_email": "support@acmecorp.com",
|
||||
"is_active": "true"
|
||||
}
|
||||
}' localhost:50051 multieko2.tables_data.TablesData/PostTableData
|
||||
}' localhost:50051 komp_ac.tables_data.TablesData/PostTableData
|
||||
{
|
||||
"success": true,
|
||||
"message": "Data inserted successfully",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"support_email": "updated-support@acmecorp.com",
|
||||
"is_active": "false"
|
||||
}
|
||||
}' localhost:50051 multieko2.tables_data.TablesData/PutTableData
|
||||
}' localhost:50051 komp_ac.tables_data.TablesData/PutTableData
|
||||
{
|
||||
"success": true,
|
||||
"message": "Data updated successfully",
|
||||
@@ -28,7 +28,7 @@
|
||||
"firma": "1",
|
||||
"is_active": "true"
|
||||
}
|
||||
}' localhost:50051 multieko2.tables_data.TablesData/PutTableData
|
||||
}' localhost:50051 komp_ac.tables_data.TablesData/PutTableData
|
||||
{
|
||||
"success": true,
|
||||
"message": "Data updated successfully",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/tables_data/handlers/delete_table_data.rs
|
||||
use tonic::Status;
|
||||
use sqlx::PgPool;
|
||||
use common::proto::multieko2::tables_data::{DeleteTableDataRequest, DeleteTableDataResponse};
|
||||
use common::proto::komp_ac::tables_data::{DeleteTableDataRequest, DeleteTableDataResponse};
|
||||
use crate::shared::schema_qualifier::qualify_table_name_for_data; // Import schema qualifier
|
||||
|
||||
pub async fn delete_table_data(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use tonic::Status;
|
||||
use sqlx::{PgPool, Row};
|
||||
use std::collections::HashMap;
|
||||
use common::proto::multieko2::tables_data::{GetTableDataRequest, GetTableDataResponse};
|
||||
use common::proto::komp_ac::tables_data::{GetTableDataRequest, GetTableDataResponse};
|
||||
use crate::shared::schema_qualifier::qualify_table_name_for_data;
|
||||
|
||||
pub async fn get_table_data(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/tables_data/handlers/get_table_data_by_position.rs
|
||||
use tonic::Status;
|
||||
use sqlx::PgPool;
|
||||
use common::proto::multieko2::tables_data::{
|
||||
use common::proto::komp_ac::tables_data::{
|
||||
GetTableDataByPositionRequest, GetTableDataRequest, GetTableDataResponse
|
||||
};
|
||||
use super::get_table_data;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// src/tables_data/handlers/get_table_data_count.rs
|
||||
use tonic::Status;
|
||||
use sqlx::PgPool;
|
||||
use common::proto::multieko2::common::CountResponse;
|
||||
use common::proto::multieko2::tables_data::GetTableDataCountRequest;
|
||||
use common::proto::komp_ac::common::CountResponse;
|
||||
use common::proto::komp_ac::tables_data::GetTableDataCountRequest;
|
||||
use crate::shared::schema_qualifier::qualify_table_name_for_data; // 1. IMPORT THE FUNCTION
|
||||
|
||||
pub async fn get_table_data_count(
|
||||
|
||||
@@ -4,7 +4,7 @@ use tonic::Status;
|
||||
use sqlx::{PgPool, Arguments};
|
||||
use sqlx::postgres::PgArguments;
|
||||
use chrono::{DateTime, Utc};
|
||||
use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse};
|
||||
use common::proto::komp_ac::tables_data::{PostTableDataRequest, PostTableDataResponse};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use prost_types::value::Kind;
|
||||
@@ -12,12 +12,18 @@ use rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::steel::server::execution::{self, Value};
|
||||
use crate::steel::server::functions::SteelContext;
|
||||
|
||||
use crate::indexer::{IndexCommand, IndexCommandData};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
/// Inserts new record into a table with validation through associated scripts.
|
||||
///
|
||||
/// This function handles creating new records in dynamically defined tables while:
|
||||
/// - Validating permissions and column existence
|
||||
/// - Executing validation scripts for computed columns
|
||||
/// - Performing type-safe database insertions
|
||||
/// - Maintaining search index consistency
|
||||
pub async fn post_table_data(
|
||||
db_pool: &PgPool,
|
||||
request: PostTableDataRequest,
|
||||
@@ -26,6 +32,7 @@ pub async fn post_table_data(
|
||||
let profile_name = request.profile_name;
|
||||
let table_name = request.table_name;
|
||||
|
||||
// Fetch schema and table metadata
|
||||
let schema = sqlx::query!(
|
||||
"SELECT id FROM schemas WHERE name = $1",
|
||||
profile_name
|
||||
@@ -48,6 +55,7 @@ pub async fn post_table_data(
|
||||
|
||||
let table_def = table_def.ok_or_else(|| Status::not_found("Table not found"))?;
|
||||
|
||||
// Parse column definitions from JSON format
|
||||
let columns_json: Vec<String> = serde_json::from_value(table_def.columns.clone())
|
||||
.map_err(|e| Status::internal(format!("Column parsing error: {}", e)))?;
|
||||
|
||||
@@ -62,6 +70,7 @@ pub async fn post_table_data(
|
||||
columns.push((name, sql_type));
|
||||
}
|
||||
|
||||
// Build list of valid system columns (foreign keys and special columns)
|
||||
let fk_columns = sqlx::query!(
|
||||
r#"SELECT ltd.table_name
|
||||
FROM table_definition_links tdl
|
||||
@@ -80,6 +89,7 @@ pub async fn post_table_data(
|
||||
|
||||
let system_columns_set: std::collections::HashSet<_> = system_columns.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
// Validate all requested columns exist and are insertable
|
||||
let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect();
|
||||
for key in request.data.keys() {
|
||||
if !system_columns_set.contains(key.as_str()) &&
|
||||
@@ -88,6 +98,7 @@ pub async fn post_table_data(
|
||||
}
|
||||
}
|
||||
|
||||
// Convert proto values to strings for script execution context
|
||||
let mut string_data_for_scripts = HashMap::new();
|
||||
for (key, proto_value) in &request.data {
|
||||
let str_val = match &proto_value.kind {
|
||||
@@ -108,6 +119,7 @@ pub async fn post_table_data(
|
||||
string_data_for_scripts.insert(key.clone(), str_val);
|
||||
}
|
||||
|
||||
// Execute validation scripts for computed columns
|
||||
let scripts = sqlx::query!(
|
||||
"SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1",
|
||||
table_def.id
|
||||
@@ -119,50 +131,66 @@ pub async fn post_table_data(
|
||||
for script_record in scripts {
|
||||
let target_column = script_record.target_column;
|
||||
|
||||
// All script target columns are required for INSERT operations
|
||||
let user_value = string_data_for_scripts.get(&target_column)
|
||||
.ok_or_else(|| Status::invalid_argument(
|
||||
format!("Script target column '{}' is required", target_column)
|
||||
))?;
|
||||
|
||||
let context = SteelContext {
|
||||
current_table: table_name.clone(),
|
||||
schema_id,
|
||||
schema_name: profile_name.clone(),
|
||||
row_data: string_data_for_scripts.clone(),
|
||||
db_pool: Arc::new(db_pool.clone()),
|
||||
};
|
||||
|
||||
|
||||
// Execute script to calculate expected value
|
||||
let script_result = execution::execute_script(
|
||||
script_record.script,
|
||||
"STRINGS",
|
||||
Arc::new(db_pool.clone()),
|
||||
context,
|
||||
schema_id,
|
||||
profile_name.clone(),
|
||||
table_name.clone(),
|
||||
string_data_for_scripts.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Status::invalid_argument(
|
||||
format!("Script execution failed for '{}': {}", target_column, e)
|
||||
))?;
|
||||
|
||||
let Value::Strings(mut script_output) = script_result else {
|
||||
return Err(Status::internal("Script must return string values"));
|
||||
};
|
||||
let Value::Strings(mut script_output) = script_result;
|
||||
|
||||
let expected_value = script_output.pop()
|
||||
.ok_or_else(|| Status::internal("Script returned no values"))?;
|
||||
|
||||
if user_value != &expected_value {
|
||||
// Perform numeric comparison for decimal values to handle equivalent representations
|
||||
// (e.g., "76.5" should equal "76.50")
|
||||
let user_decimal = Decimal::from_str(user_value).map_err(|_| {
|
||||
Status::invalid_argument(format!(
|
||||
"Invalid decimal format provided for column '{}': {}",
|
||||
target_column, user_value
|
||||
))
|
||||
})?;
|
||||
|
||||
let expected_decimal = Decimal::from_str(&expected_value).map_err(|_| {
|
||||
Status::internal(format!(
|
||||
"Script for column '{}' produced an invalid decimal format: {}",
|
||||
target_column, expected_value
|
||||
))
|
||||
})?;
|
||||
|
||||
if user_decimal != expected_decimal {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Validation failed for column '{}': Expected '{}', Got '{}'",
|
||||
"Validation failed for column '{}': Script calculated '{}', but user provided '{}'",
|
||||
target_column, expected_value, user_value
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Build parameterized INSERT query with type-safe parameter binding
|
||||
let mut params = PgArguments::default();
|
||||
let mut columns_list = Vec::new();
|
||||
let mut placeholders = Vec::new();
|
||||
let mut param_idx = 1;
|
||||
|
||||
for (col, proto_value) in request.data {
|
||||
// Determine SQL type for proper parameter binding
|
||||
let sql_type = if system_columns_set.contains(col.as_str()) {
|
||||
match col.as_str() {
|
||||
"deleted" => "BOOLEAN",
|
||||
@@ -176,6 +204,7 @@ pub async fn post_table_data(
|
||||
.ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))?
|
||||
};
|
||||
|
||||
// Handle NULL values first
|
||||
let kind = match &proto_value.kind {
|
||||
None | Some(Kind::NullValue(_)) => {
|
||||
match sql_type {
|
||||
@@ -196,102 +225,107 @@ pub async fn post_table_data(
|
||||
Some(k) => k,
|
||||
};
|
||||
|
||||
if sql_type == "TEXT" {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let trimmed_value = value.trim();
|
||||
// Handle typed values with validation
|
||||
match sql_type {
|
||||
"TEXT" => {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let trimmed_value = value.trim();
|
||||
|
||||
if trimmed_value.is_empty() {
|
||||
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
if col == "telefon" && trimmed_value.len() > 15 {
|
||||
return Err(Status::internal(format!("Value too long for {}", col)));
|
||||
}
|
||||
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
|
||||
}
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "BOOLEAN" {
|
||||
if let Kind::BoolValue(val) = kind {
|
||||
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "TIMESTAMPTZ" {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
|
||||
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "BIGINT" {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
|
||||
// Simple universal check: try the conversion and verify it's reversible
|
||||
// This handles ALL edge cases: infinity, NaN, overflow, underflow, precision loss
|
||||
let as_i64 = *val as i64;
|
||||
if (as_i64 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
|
||||
}
|
||||
|
||||
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "INTEGER" {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
|
||||
// Simple universal check: try the conversion and verify it's reversible
|
||||
// This handles ALL edge cases: infinity, NaN, overflow, underflow, precision loss
|
||||
let as_i32 = *val as i32;
|
||||
if (as_i32 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
|
||||
}
|
||||
|
||||
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type.starts_with("NUMERIC") {
|
||||
// MODIFIED: This block is now stricter.
|
||||
let decimal_val = match kind {
|
||||
Kind::StringValue(s) => {
|
||||
let trimmed = s.trim();
|
||||
if trimmed.is_empty() {
|
||||
None // Treat empty string as NULL
|
||||
if trimmed_value.is_empty() {
|
||||
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
// This is the only valid path: parse from a string.
|
||||
Some(Decimal::from_str(trimmed).map_err(|_| {
|
||||
Status::invalid_argument(format!(
|
||||
"Invalid decimal string format for column '{}': {}",
|
||||
col, s
|
||||
))
|
||||
})?)
|
||||
// Apply business rule validation (e.g., phone number length)
|
||||
if col == "telefon" && trimmed_value.len() > 15 {
|
||||
return Err(Status::internal(format!("Value too long for {}", col)));
|
||||
}
|
||||
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
|
||||
}
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
|
||||
}
|
||||
// CATCH-ALL: Reject NumberValue, BoolValue, etc. for NUMERIC fields.
|
||||
_ => {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Expected a string representation for decimal column '{}', but received a different type.",
|
||||
col
|
||||
)));
|
||||
},
|
||||
"BOOLEAN" => {
|
||||
if let Kind::BoolValue(val) = kind {
|
||||
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
|
||||
}
|
||||
};
|
||||
},
|
||||
"TIMESTAMPTZ" => {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
|
||||
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
"BIGINT" => {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
|
||||
params.add(decimal_val).map_err(|e| {
|
||||
Status::invalid_argument(format!(
|
||||
"Failed to add decimal parameter for {}: {}",
|
||||
col, e
|
||||
))
|
||||
})?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type)));
|
||||
// Validate conversion is reversible to catch overflow, underflow, and precision loss
|
||||
let as_i64 = *val as i64;
|
||||
if (as_i64 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
|
||||
}
|
||||
|
||||
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
"INTEGER" => {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
|
||||
// Validate conversion is reversible to catch overflow, underflow, and precision loss
|
||||
let as_i32 = *val as i32;
|
||||
if (as_i32 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
|
||||
}
|
||||
|
||||
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
s if s.starts_with("NUMERIC") => {
|
||||
// Decimal values must be provided as strings for precise parsing
|
||||
let decimal_val = match kind {
|
||||
Kind::StringValue(s) => {
|
||||
let trimmed = s.trim();
|
||||
if trimmed.is_empty() {
|
||||
None // Treat empty string as NULL
|
||||
} else {
|
||||
Some(Decimal::from_str(trimmed).map_err(|_| {
|
||||
Status::invalid_argument(format!(
|
||||
"Invalid decimal string format for column '{}': {}",
|
||||
col, s
|
||||
))
|
||||
})?)
|
||||
}
|
||||
}
|
||||
// Reject other types to ensure precise decimal handling
|
||||
_ => {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Expected a string representation for decimal column '{}', but received a different type.",
|
||||
col
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
params.add(decimal_val).map_err(|e| {
|
||||
Status::invalid_argument(format!(
|
||||
"Failed to add decimal parameter for {}: {}",
|
||||
col, e
|
||||
))
|
||||
})?;
|
||||
},
|
||||
_ => return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type))),
|
||||
}
|
||||
|
||||
columns_list.push(format!("\"{}\"", col));
|
||||
@@ -303,6 +337,7 @@ pub async fn post_table_data(
|
||||
return Err(Status::invalid_argument("No valid columns to insert"));
|
||||
}
|
||||
|
||||
// Execute the INSERT query
|
||||
let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(
|
||||
db_pool,
|
||||
&profile_name,
|
||||
@@ -324,6 +359,7 @@ pub async fn post_table_data(
|
||||
let inserted_id = match result {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
// Handle specific database errors with user-friendly messages
|
||||
if let Some(db_err) = e.as_database_error() {
|
||||
if db_err.code() == Some(std::borrow::Cow::Borrowed("22P02")) ||
|
||||
db_err.code() == Some(std::borrow::Cow::Borrowed("22003")) {
|
||||
@@ -342,6 +378,7 @@ pub async fn post_table_data(
|
||||
}
|
||||
};
|
||||
|
||||
// Queue record for search index update
|
||||
let command = IndexCommand::AddOrUpdate(IndexCommandData {
|
||||
table_name: table_name.clone(),
|
||||
row_id: inserted_id,
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
// src/tables_data/handlers/put_table_data.rs
|
||||
|
||||
use tonic::Status;
|
||||
use sqlx::{PgPool, Arguments};
|
||||
use sqlx::{PgPool, Arguments, Row};
|
||||
use sqlx::postgres::PgArguments;
|
||||
use chrono::{DateTime, Utc};
|
||||
use common::proto::multieko2::tables_data::{PutTableDataRequest, PutTableDataResponse};
|
||||
use std::collections::HashMap;
|
||||
use common::proto::komp_ac::tables_data::{PutTableDataRequest, PutTableDataResponse};
|
||||
|
||||
use std::sync::Arc;
|
||||
use prost_types::value::Kind;
|
||||
use rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::steel::server::execution::{self, Value};
|
||||
use crate::steel::server::functions::SteelContext;
|
||||
use crate::indexer::{IndexCommand, IndexCommandData};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
/// Updates table data with validation through associated scripts.
|
||||
///
|
||||
/// This function handles updating records in dynamically defined tables while:
|
||||
/// - Validating permissions and column existence
|
||||
/// - Executing validation scripts for computed columns
|
||||
/// - Performing type-safe database updates
|
||||
/// - Maintaining search index consistency
|
||||
pub async fn put_table_data(
|
||||
db_pool: &PgPool,
|
||||
request: PutTableDataRequest,
|
||||
@@ -26,7 +33,6 @@ pub async fn put_table_data(
|
||||
let table_name = request.table_name;
|
||||
let record_id = request.id;
|
||||
|
||||
// An update with no fields is a no-op; we can return success early.
|
||||
if request.data.is_empty() {
|
||||
return Ok(PutTableDataResponse {
|
||||
success: true,
|
||||
@@ -35,30 +41,22 @@ pub async fn put_table_data(
|
||||
});
|
||||
}
|
||||
|
||||
// --- Start of logic copied and adapted from post_table_data ---
|
||||
|
||||
let schema = sqlx::query!(
|
||||
"SELECT id FROM schemas WHERE name = $1",
|
||||
profile_name
|
||||
)
|
||||
.fetch_optional(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Profile lookup error: {}", e)))?;
|
||||
|
||||
let schema_id = schema.ok_or_else(|| Status::not_found("Profile not found"))?.id;
|
||||
// Fetch schema and table metadata
|
||||
let schema = sqlx::query!("SELECT id FROM schemas WHERE name = $1", profile_name)
|
||||
.fetch_optional(db_pool).await
|
||||
.map_err(|e| Status::internal(format!("Profile lookup error: {}", e)))?
|
||||
.ok_or_else(|| Status::not_found("Profile not found"))?;
|
||||
let schema_id = schema.id;
|
||||
|
||||
let table_def = sqlx::query!(
|
||||
r#"SELECT id, columns FROM table_definitions
|
||||
WHERE schema_id = $1 AND table_name = $2"#,
|
||||
schema_id,
|
||||
table_name
|
||||
"SELECT id, columns FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
|
||||
schema_id, table_name
|
||||
)
|
||||
.fetch_optional(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Table lookup error: {}", e)))?;
|
||||
|
||||
let table_def = table_def.ok_or_else(|| Status::not_found("Table not found"))?;
|
||||
.fetch_optional(db_pool).await
|
||||
.map_err(|e| Status::internal(format!("Table lookup error: {}", e)))?
|
||||
.ok_or_else(|| Status::not_found("Table not found"))?;
|
||||
|
||||
// Parse column definitions from JSON format
|
||||
let columns_json: Vec<String> = serde_json::from_value(table_def.columns.clone())
|
||||
.map_err(|e| Status::internal(format!("Column parsing error: {}", e)))?;
|
||||
|
||||
@@ -73,6 +71,7 @@ pub async fn put_table_data(
|
||||
columns.push((name, sql_type));
|
||||
}
|
||||
|
||||
// Build list of valid system columns (foreign keys and special columns)
|
||||
let fk_columns = sqlx::query!(
|
||||
r#"SELECT ltd.table_name
|
||||
FROM table_definition_links tdl
|
||||
@@ -85,13 +84,14 @@ pub async fn put_table_data(
|
||||
.map_err(|e| Status::internal(format!("Foreign key lookup error: {}", e)))?;
|
||||
|
||||
let mut system_columns = vec!["deleted".to_string()];
|
||||
for fk in fk_columns {
|
||||
for fk in &fk_columns {
|
||||
system_columns.push(format!("{}_id", fk.table_name));
|
||||
}
|
||||
|
||||
// Validate all requested columns exist and are editable
|
||||
let system_columns_set: std::collections::HashSet<_> = system_columns.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect();
|
||||
|
||||
for key in request.data.keys() {
|
||||
if !system_columns_set.contains(key.as_str()) &&
|
||||
!user_columns.contains(&&key.to_string()) {
|
||||
@@ -99,77 +99,191 @@ pub async fn put_table_data(
|
||||
}
|
||||
}
|
||||
|
||||
let mut string_data_for_scripts = HashMap::new();
|
||||
for (key, proto_value) in &request.data {
|
||||
let str_val = match &proto_value.kind {
|
||||
Some(Kind::StringValue(s)) => {
|
||||
let trimmed = s.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
trimmed.to_string()
|
||||
},
|
||||
Some(Kind::NumberValue(n)) => n.to_string(),
|
||||
Some(Kind::BoolValue(b)) => b.to_string(),
|
||||
Some(Kind::NullValue(_)) | None => continue,
|
||||
Some(Kind::StructValue(_)) | Some(Kind::ListValue(_)) => {
|
||||
return Err(Status::invalid_argument(format!("Unsupported type for script validation in column '{}'", key)));
|
||||
}
|
||||
};
|
||||
string_data_for_scripts.insert(key.clone(), str_val);
|
||||
// Fetch validation scripts for this table
|
||||
let scripts = sqlx::query!("SELECT id, target_column, script FROM table_scripts WHERE table_definitions_id = $1", table_def.id)
|
||||
.fetch_all(db_pool).await
|
||||
.map_err(|e| Status::internal(format!("Failed to fetch scripts: {}", e)))?;
|
||||
|
||||
// Determine minimal set of columns needed for script execution context
|
||||
let mut required_columns = std::collections::HashSet::new();
|
||||
|
||||
// Always need: id, target columns of scripts, and columns being updated
|
||||
required_columns.insert("id".to_string());
|
||||
for script_record in &scripts {
|
||||
required_columns.insert(script_record.target_column.clone());
|
||||
}
|
||||
for key in request.data.keys() {
|
||||
required_columns.insert(key.clone());
|
||||
}
|
||||
|
||||
let scripts = sqlx::query!(
|
||||
"SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1",
|
||||
table_def.id
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Failed to fetch scripts: {}", e)))?;
|
||||
// Add columns referenced by scripts based on pre-computed dependencies
|
||||
if !scripts.is_empty() {
|
||||
let script_ids: Vec<i64> = scripts.iter().map(|s| s.id).collect();
|
||||
|
||||
for script_record in scripts {
|
||||
let target_column = script_record.target_column;
|
||||
let dependencies = sqlx::query!(
|
||||
r#"SELECT sd.target_table_id, sd.dependency_type, sd.context_info, td.table_name as target_table
|
||||
FROM script_dependencies sd
|
||||
JOIN table_definitions td ON sd.target_table_id = td.id
|
||||
WHERE sd.script_id = ANY($1)"#,
|
||||
&script_ids
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Failed to fetch script dependencies: {}", e)))?;
|
||||
|
||||
if let Some(user_value) = string_data_for_scripts.get(&target_column) {
|
||||
let context = SteelContext {
|
||||
current_table: table_name.clone(),
|
||||
schema_id,
|
||||
schema_name: profile_name.clone(),
|
||||
row_data: string_data_for_scripts.clone(),
|
||||
db_pool: Arc::new(db_pool.clone()),
|
||||
};
|
||||
|
||||
let script_result = execution::execute_script(
|
||||
script_record.script,
|
||||
"STRINGS",
|
||||
Arc::new(db_pool.clone()),
|
||||
context,
|
||||
)
|
||||
.map_err(|e| Status::invalid_argument(
|
||||
format!("Script execution failed for '{}': {}", target_column, e)
|
||||
))?;
|
||||
|
||||
let Value::Strings(mut script_output) = script_result else {
|
||||
return Err(Status::internal("Script must return string values"));
|
||||
};
|
||||
|
||||
let expected_value = script_output.pop()
|
||||
.ok_or_else(|| Status::internal("Script returned no values"))?;
|
||||
|
||||
if user_value != &expected_value {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Validation failed for column '{}': Expected '{}', Got '{}'",
|
||||
target_column, expected_value, user_value
|
||||
)));
|
||||
for dep in dependencies {
|
||||
if dep.target_table == table_name {
|
||||
// Script references this table - add specific columns it uses
|
||||
match dep.dependency_type.as_str() {
|
||||
"column_access" | "indexed_access" => {
|
||||
if let Some(context) = dep.context_info {
|
||||
if let Some(column) = context.get("column").and_then(|v| v.as_str()) {
|
||||
required_columns.insert(column.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {} // SQL queries handled differently
|
||||
}
|
||||
} else {
|
||||
// Script references linked table - add foreign key column
|
||||
let fk_column = format!("{}_id", dep.target_table.split('_').last().unwrap_or(&dep.target_table));
|
||||
required_columns.insert(fk_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch current record state with only required columns for efficiency
|
||||
let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(db_pool, &profile_name, &table_name).await?;
|
||||
|
||||
let columns_clause = required_columns
|
||||
.iter()
|
||||
.map(|name| format!("COALESCE(\"{0}\"::TEXT, '') AS \"{0}\"", name))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
let select_sql = format!("SELECT {} FROM {} WHERE id = $1", columns_clause, qualified_table);
|
||||
|
||||
let current_row = sqlx::query(&select_sql).bind(record_id).fetch_optional(db_pool).await
|
||||
.map_err(|e| Status::internal(format!("Failed to fetch current row state: {}", e)))?
|
||||
.ok_or_else(|| Status::not_found("Record not found"))?;
|
||||
|
||||
let mut current_row_data = HashMap::new();
|
||||
for col_name in &required_columns {
|
||||
let value: String = current_row.try_get(col_name.as_str()).unwrap_or_default();
|
||||
current_row_data.insert(col_name.clone(), value);
|
||||
}
|
||||
|
||||
// Extract and normalize update data from request
|
||||
let mut update_data = HashMap::new();
|
||||
for (key, proto_value) in &request.data {
|
||||
let str_val = match &proto_value.kind {
|
||||
Some(Kind::StringValue(s)) => s.trim().to_string(),
|
||||
Some(Kind::NumberValue(n)) => n.to_string(),
|
||||
Some(Kind::BoolValue(b)) => b.to_string(),
|
||||
Some(Kind::NullValue(_)) | None => String::new(),
|
||||
_ => return Err(Status::invalid_argument(format!("Unsupported type for column '{}'", key))),
|
||||
};
|
||||
update_data.insert(key.clone(), str_val);
|
||||
}
|
||||
|
||||
// Create final context by merging current data with updates
|
||||
let mut final_context_data = current_row_data.clone();
|
||||
final_context_data.extend(update_data.clone());
|
||||
|
||||
// Execute and validate scripts for computed columns
|
||||
for script_record in scripts {
|
||||
let target_column = script_record.target_column;
|
||||
|
||||
// Determine SQL type for type-aware validation
|
||||
let target_sql_type = if let Some((_, stype)) = columns.iter().find(|(name, _)| name == &target_column) {
|
||||
stype.as_str()
|
||||
} else {
|
||||
"TEXT" // Default fallback for system columns
|
||||
};
|
||||
|
||||
// Execute script with current context
|
||||
let script_result = execution::execute_script(
|
||||
script_record.script.clone(),
|
||||
"STRINGS",
|
||||
Arc::new(db_pool.clone()),
|
||||
schema_id,
|
||||
profile_name.clone(),
|
||||
table_name.clone(),
|
||||
final_context_data.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Script execution failed for '{}': {:?}", target_column, e);
|
||||
Status::invalid_argument(format!("Script execution failed for '{}': {}", target_column, e))
|
||||
})?;
|
||||
|
||||
let Value::Strings(mut script_output_vec) = script_result;
|
||||
|
||||
let script_output = script_output_vec.pop().ok_or_else(|| Status::internal("Script returned no values"))?;
|
||||
|
||||
if update_data.contains_key(&target_column) {
|
||||
// Case A: Column is being updated - validate user input against script output
|
||||
let user_value = update_data.get(&target_column).unwrap();
|
||||
|
||||
// Perform type-aware comparison based on SQL column type
|
||||
let values_match = match target_sql_type {
|
||||
s if s.starts_with("NUMERIC") => {
|
||||
let user_decimal = Decimal::from_str(user_value).map_err(|_| Status::invalid_argument(format!("Invalid decimal format for column '{}'", target_column)))?;
|
||||
let script_decimal = Decimal::from_str(&script_output).map_err(|_| Status::internal(format!("Script for '{}' produced invalid decimal", target_column)))?;
|
||||
user_decimal == script_decimal
|
||||
},
|
||||
"INTEGER" | "BIGINT" => {
|
||||
let user_int: i64 = user_value.parse().map_err(|_| Status::invalid_argument(format!("Invalid integer format for column '{}'", target_column)))?;
|
||||
let script_int: i64 = script_output.parse().map_err(|_| Status::internal(format!("Script for '{}' produced invalid integer", target_column)))?;
|
||||
user_int == script_int
|
||||
},
|
||||
"BOOLEAN" => {
|
||||
let user_bool: bool = user_value.parse().map_err(|_| Status::invalid_argument(format!("Invalid boolean format for column '{}'", target_column)))?;
|
||||
let script_bool: bool = script_output.parse().map_err(|_| Status::internal(format!("Script for '{}' produced invalid boolean", target_column)))?;
|
||||
user_bool == script_bool
|
||||
},
|
||||
_ => user_value == &script_output
|
||||
};
|
||||
|
||||
if !values_match {
|
||||
return Err(Status::invalid_argument(format!("Validation failed for column '{}': Script calculated '{}', but user provided '{}'", target_column, script_output, user_value)));
|
||||
}
|
||||
} else {
|
||||
// Case B: Column is NOT being updated - prevent unauthorized script-triggered changes
|
||||
let current_value = current_row_data.get(&target_column).cloned().unwrap_or_default();
|
||||
|
||||
let values_match = match target_sql_type {
|
||||
s if s.starts_with("NUMERIC") => {
|
||||
let current_decimal = Decimal::from_str(¤t_value).unwrap_or_default();
|
||||
let script_decimal = Decimal::from_str(&script_output).unwrap_or_default();
|
||||
current_decimal == script_decimal
|
||||
},
|
||||
"INTEGER" | "BIGINT" => {
|
||||
let current_int: i64 = current_value.parse().unwrap_or_default();
|
||||
let script_int: i64 = script_output.parse().unwrap_or_default();
|
||||
current_int == script_int
|
||||
},
|
||||
"BOOLEAN" => {
|
||||
let current_bool: bool = current_value.parse().unwrap_or(false);
|
||||
let script_bool: bool = script_output.parse().unwrap_or(false);
|
||||
current_bool == script_bool
|
||||
},
|
||||
_ => current_value == script_output
|
||||
};
|
||||
|
||||
if !values_match {
|
||||
return Err(Status::failed_precondition(format!("Script for column '{}' was triggered and would change its value from '{}' to '{}'. To apply this change, please include '{}' in your update request.", target_column, current_value, script_output, target_column)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build parameterized UPDATE query with type-safe parameter binding
|
||||
let mut params = PgArguments::default();
|
||||
let mut set_clauses = Vec::new();
|
||||
let mut param_idx = 1;
|
||||
|
||||
for (col, proto_value) in request.data {
|
||||
// Determine SQL type for proper parameter binding
|
||||
let sql_type = if system_columns_set.contains(col.as_str()) {
|
||||
match col.as_str() {
|
||||
"deleted" => "BOOLEAN",
|
||||
@@ -183,6 +297,7 @@ pub async fn put_table_data(
|
||||
.ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))?
|
||||
};
|
||||
|
||||
// Handle NULL values first
|
||||
let kind = match &proto_value.kind {
|
||||
None | Some(Kind::NullValue(_)) => {
|
||||
match sql_type {
|
||||
@@ -202,98 +317,103 @@ pub async fn put_table_data(
|
||||
Some(k) => k,
|
||||
};
|
||||
|
||||
if sql_type == "TEXT" {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let trimmed_value = value.trim();
|
||||
|
||||
if trimmed_value.is_empty() {
|
||||
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
if col == "telefon" && trimmed_value.len() > 15 {
|
||||
return Err(Status::internal(format!("Value too long for {}", col)));
|
||||
}
|
||||
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
|
||||
}
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "BOOLEAN" {
|
||||
if let Kind::BoolValue(val) = kind {
|
||||
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "TIMESTAMPTZ" {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
|
||||
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "BIGINT" {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
let as_i64 = *val as i64;
|
||||
if (as_i64 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
|
||||
}
|
||||
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type == "INTEGER" {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
let as_i32 = *val as i32;
|
||||
if (as_i32 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
|
||||
}
|
||||
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
} else if sql_type.starts_with("NUMERIC") {
|
||||
let decimal_val = match kind {
|
||||
Kind::StringValue(s) => {
|
||||
let trimmed = s.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
// Handle typed values with validation
|
||||
match sql_type {
|
||||
"TEXT" => {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let trimmed_value = value.trim();
|
||||
if trimmed_value.is_empty() {
|
||||
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
Some(Decimal::from_str(trimmed).map_err(|_| {
|
||||
Status::invalid_argument(format!(
|
||||
"Invalid decimal string format for column '{}': {}",
|
||||
col, s
|
||||
))
|
||||
})?)
|
||||
// Apply business rule validation (e.g., phone number length)
|
||||
if col == "telefon" && trimmed_value.len() > 15 {
|
||||
return Err(Status::internal(format!("Value too long for {}", col)));
|
||||
}
|
||||
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
|
||||
}
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
|
||||
}
|
||||
_ => {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Expected a string representation for decimal column '{}', but received a different type.",
|
||||
col
|
||||
)));
|
||||
},
|
||||
"BOOLEAN" => {
|
||||
if let Kind::BoolValue(val) = kind {
|
||||
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
|
||||
}
|
||||
};
|
||||
params.add(decimal_val).map_err(|e| {
|
||||
Status::invalid_argument(format!(
|
||||
"Failed to add decimal parameter for {}: {}",
|
||||
col, e
|
||||
))
|
||||
})?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type)));
|
||||
},
|
||||
"TIMESTAMPTZ" => {
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
|
||||
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
"BIGINT" => {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
let as_i64 = *val as i64;
|
||||
if (as_i64 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
|
||||
}
|
||||
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
"INTEGER" => {
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
let as_i32 = *val as i32;
|
||||
if (as_i32 as f64) != *val {
|
||||
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
|
||||
}
|
||||
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
s if s.starts_with("NUMERIC") => {
|
||||
let decimal_val = match kind {
|
||||
Kind::StringValue(s) => {
|
||||
let trimmed = s.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Decimal::from_str(trimmed).map_err(|_| {
|
||||
Status::invalid_argument(format!(
|
||||
"Invalid decimal string format for column '{}': {}",
|
||||
col, s
|
||||
))
|
||||
})?)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"Expected a string representation for decimal column '{}', but received a different type.",
|
||||
col
|
||||
)));
|
||||
}
|
||||
};
|
||||
params.add(decimal_val).map_err(|e| {
|
||||
Status::invalid_argument(format!(
|
||||
"Failed to add decimal parameter for {}: {}",
|
||||
col, e
|
||||
))
|
||||
})?;
|
||||
},
|
||||
_ => return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type))),
|
||||
}
|
||||
|
||||
set_clauses.push(format!("\"{}\" = ${}", col, param_idx));
|
||||
param_idx += 1;
|
||||
}
|
||||
|
||||
// --- End of copied logic ---
|
||||
|
||||
if set_clauses.is_empty() {
|
||||
return Ok(PutTableDataResponse {
|
||||
success: true,
|
||||
@@ -302,13 +422,7 @@ pub async fn put_table_data(
|
||||
});
|
||||
}
|
||||
|
||||
let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(
|
||||
db_pool,
|
||||
&profile_name,
|
||||
&table_name,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Execute the UPDATE query
|
||||
let set_clause = set_clauses.join(", ");
|
||||
let sql = format!(
|
||||
"UPDATE {} SET {} WHERE id = ${} RETURNING id",
|
||||
@@ -327,6 +441,7 @@ pub async fn put_table_data(
|
||||
Ok(Some(id)) => id,
|
||||
Ok(None) => return Err(Status::not_found("Record not found")),
|
||||
Err(e) => {
|
||||
// Handle specific database errors with user-friendly messages
|
||||
if let Some(db_err) = e.as_database_error() {
|
||||
if db_err.code() == Some(std::borrow::Cow::Borrowed("22P02")) ||
|
||||
db_err.code() == Some(std::borrow::Cow::Borrowed("22003")) {
|
||||
@@ -339,6 +454,7 @@ pub async fn put_table_data(
|
||||
}
|
||||
};
|
||||
|
||||
// Queue record for search index update
|
||||
let command = IndexCommand::AddOrUpdate(IndexCommandData {
|
||||
table_name: table_name.clone(),
|
||||
row_id: updated_id,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// tests/mod.rs
|
||||
|
||||
pub mod tables_data;
|
||||
pub mod common;
|
||||
pub mod table_script;
|
||||
pub mod table_definition;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// tests/table_definition/post_table_definition_test.rs
|
||||
|
||||
// Keep all your normal use statements
|
||||
use common::proto::multieko2::table_definition::{
|
||||
use common::proto::komp_ac::table_definition::{
|
||||
ColumnDefinition, PostTableDefinitionRequest, TableLink,
|
||||
};
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user