1
0
mirror of https://github.com/Bayselonarrend/OpenIntegrations.git synced 2025-08-10 22:41:43 +02:00

Начало PG

This commit is contained in:
Anton Titovets
2025-01-26 19:13:40 +03:00
parent 31524c781b
commit 964828efa2
9 changed files with 1844 additions and 0 deletions

1273
src/addins/postgre/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
[package]
name = "opi_postgres"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[profile.release]
lto = "fat" # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = "abort" # Abort on panic
strip = true # Automatically strip symbols from the binary.
opt-level = "z"
[dependencies]
addin1c = "0.5.0"
postgres = { version = "0.19.9"}
serde_json = "1.0"
base64 = "0.22.1"
serde_postgres = { version = "0.2.0" }

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<bundle xmlns='http://v8.1c.ru/8.2/addin/bundle' name='OPIADDIN'>
<component os='Windows' path='AddIn_x86_windows.dll' type='native' arch='i386' />
<component os='Windows' path='AddIn_x64_windows.dll' type='native' arch='x86_64' />
<component os='Linux' path='AddIn_x86_linux.so' type='native' arch='i386' />
<component os='Linux' path='AddIn_x64_linux.so' type='native' arch='x86_64' />
</bundle>

69
src/addins/postgre/release.bat vendored Normal file
View File

@@ -0,0 +1,69 @@
@echo off
:: Установить переменную
set CARGO_NAME=opi_sqlite
set LIB_NAME=OPI_SQLite
set OPENSSL_DIR=C:\msys64\mingw64
set OPENSSL_LIB_DIR=%OPENSSL_DIR%\lib
set OPENSSL_INCLUDE_DIR=%OPENSSL_DIR%\include
:: Перейти в директорию проекта
cd /d "%~dp0"
:: Создать папку для артефактов
set OUTPUT_DIR=artifacts
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
:: Сборка для x86_64-pc-windows-msvc
cargo build --release --target x86_64-pc-windows-msvc
if errorlevel 1 goto :error
:: Сборка для x86_64-unknown-linux-gnu
cargo zigbuild --release --target x86_64-unknown-linux-gnu
if errorlevel 1 goto :error
:: Сборка для i686-pc-windows-msvc
cargo build --release --target i686-pc-windows-msvc
if errorlevel 1 goto :error
:: Сборка для i686-unknown-linux-gnu
cargo zigbuild --release --target i686-unknown-linux-gnu
if errorlevel 1 goto :error
:: Копирование файлов .dll и .so
copy /y target\x86_64-pc-windows-msvc\release\%CARGO_NAME%.dll "%OUTPUT_DIR%\AddIn_x64_windows.dll"
if errorlevel 1 goto :error
copy /y target\i686-pc-windows-msvc\release\%CARGO_NAME%.dll "%OUTPUT_DIR%\AddIn_x86_windows.dll"
if errorlevel 1 goto :error
copy /y target\x86_64-unknown-linux-gnu\release\lib%CARGO_NAME%.so "%OUTPUT_DIR%\AddIn_x64_linux.so"
if errorlevel 1 goto :error
copy /y target\i686-unknown-linux-gnu\release\lib%CARGO_NAME%.so "%OUTPUT_DIR%\AddIn_x86_linux.so"
if errorlevel 1 goto :error
copy /y MANIFEST.XML "%OUTPUT_DIR%\MANIFEST.XML"
if errorlevel 1 goto :error
:: Архивация
powershell -Command "Compress-Archive -Path '%OUTPUT_DIR%\*' -Force -DestinationPath '%LIB_NAME%.zip'"
if errorlevel 1 goto :error
copy /y "%LIB_NAME%.zip" "../../ru/OInt/addins/%LIB_NAME%.zip"
copy /y "%LIB_NAME%.zip" "../../en/OInt/addins/%LIB_NAME%.zip"
copy /y "%LIB_NAME%.zip" "../../ru/OPI/src/CommonTemplates/%LIB_NAME%/Template.addin"
copy /y "%LIB_NAME%.zip" "../../en/OPI/src/CommonTemplates/%LIB_NAME%/Template.addin"
if exist "%OUTPUT_DIR%" (
rmdir /S /Q "%OUTPUT_DIR%"
)
@echo Build and packaging completed successfully.
exit /b 0
:error
@echo An error occurred during the build or packaging process.
exit /b 1

View File

@@ -0,0 +1,176 @@
use postgres::types::{ToSql};
use serde_json::{Value, json, Map};
use serde_postgres::from_row;
use base64::{engine::general_purpose, Engine as _};
use serde_json::Value::Null;
use crate::component::AddIn;
use std::collections::HashMap;
use std::net::IpAddr;
use std::time::{SystemTime, UNIX_EPOCH};
pub fn execute_query(
add_in: &mut AddIn,
query: String,
params_json: String,
force_result: bool,
) -> String {
let mut client = match add_in.get_connection() {
Some(c) => c,
None => return format_json_error("No connection initialized"),
};
// Парсинг JSON параметров
let params: Vec<Value> = match serde_json::from_str(&params_json) {
Ok(params) => params,
Err(e) => return format_json_error(&e.to_string()),
};
let params_ref = match process_params(&params){
Ok(params) => params.iter().map(|param| param.as_ref()).collect::<Vec<&(dyn ToSql + Sync)>>(),
Err(e) => return format_json_error(&e.to_string()),
};
if query.trim_start().to_uppercase().starts_with("SELECT") || force_result {
match client.query(&query, &params_ref) {
Ok(rows) => {
let mut result = Vec::new();
for row in rows {
let row_json = match from_row(&row){
Ok(row_json) => row_json,
Err(e) => return format_json_error(&e.to_string()),
};
result.push(row_json);
}
json!({"result": true, "rows": result}).to_string()
}
Err(e) => format_json_error(&e.to_string()),
}
} else {
match client.execute(&query, &params_ref) {
Ok(_) => json!({"result": true}).to_string(),
Err(e) => format_json_error(&e.to_string()),
}
}
}
fn process_object(object: &Map<String, Value>) -> Result<Box<dyn ToSql + Sync>, String> {
if object.len() != 1 {
return Err("Object must have exactly one key-value pair specifying the type and value".to_string());
}
let (key, value) = object.iter().next().unwrap();
match key.as_str() {
"BOOL" => value
.as_bool()
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid value for BOOL".to_string()),
"\"char\"" => value
.as_i64()
.and_then(|v| i8::try_from(v).ok())
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid value for \"char\"".to_string()),
"SMALLINT" | "SMALLSERIAL" => value
.as_i64()
.and_then(|v| i16::try_from(v).ok())
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| format!("Invalid value for {}", key)),
"INT" | "SERIAL" => value
.as_i64()
.and_then(|v| i32::try_from(v).ok())
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| format!("Invalid value for {}", key)),
"OID" => value
.as_u64()
.and_then(|v| u32::try_from(v).ok())
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid value for OID".to_string()),
"BIGINT" | "BIGSERIAL" => value
.as_i64()
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| format!("Invalid value for {}", key)),
"REAL" => value
.as_f64()
.map(|v| v as f32) // Преобразование f64 в f32
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid value for REAL".to_string()),
"DOUBLE PRECISION" => value
.as_f64()
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid value for DOUBLE PRECISION".to_string()),
"VARCHAR" | "TEXT" | "CHAR" | "CITEXT" | "NAME" | "LTREE" | "LQUERY" | "LTXTQUERY" => value
.as_str()
.map(|v| Box::new(v.to_string()) as Box<dyn ToSql + Sync>)
.ok_or_else(|| format!("Invalid value for {}", key)),
"BYTEA" => value
.as_str()
.map(|blob_str| {
// Очистка строки base64 от лишних символов
let cleaned_base64 = blob_str.replace(&['\n', '\r', ' '][..], "");
general_purpose::STANDARD.decode(&cleaned_base64)
})
.and_then(|res| res.ok())
.map(|v| Box::new(v) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid base64 value for BYTEA".to_string()),
"HSTORE" => value
.as_object()
.map(|obj| {
let mut map = HashMap::new();
for (k, v) in obj.iter() {
map.insert(
k.clone(),
v.as_str().map(String::from), // Значение может быть None
);
}
Box::new(map) as Box<dyn ToSql + Sync>
})
.ok_or_else(|| "Invalid object for HSTORE".to_string()),
"TIMESTAMP" | "TIMESTAMP WITH TIME ZONE" => value
.as_i64()
.map(|v| {
let duration = UNIX_EPOCH + std::time::Duration::from_secs(v as u64);
let system_time = SystemTime::from(duration);
Box::new(system_time) as Box<dyn ToSql + Sync>
})
.ok_or_else(|| "Invalid value for TIMESTAMP".to_string()),
"INET" => value
.as_str()
.and_then(|s| s.parse::<IpAddr>().ok())
.map(|ip| Box::new(ip) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid value for INET".to_string()),
_ => Err(format!("Unsupported type: {}", key)),
}
}
/// Конвертирует JSON-параметры в Postgres-совместимые типы
fn process_params(params: &Vec<Value>) -> Result<Vec<Box<dyn ToSql + Sync>>, String> {
let mut result = Vec::new();
for param in params {
let processed: Box<dyn ToSql + Sync> = match param {
Value::Null => Box::new(Option::<i32>::None),
Value::Bool(b) => Box::new(*b),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Box::new(i)
} else if let Some(f) = n.as_f64() {
Box::new(f)
} else {
return Err("Invalid number".to_string());
}
}
Value::String(s) => Box::new(s.clone()),
Value::Object(obj) => process_object(obj)?,
_ => return Err("Unsupported parameter type".to_string()),
};
result.push(processed);
}
Ok(result)
}
fn format_json_error(error: &str) -> String {
json!({
"result": false,
"error": error
})
.to_string()
}

View File

@@ -0,0 +1,118 @@
mod methods;
use addin1c::{name, Variant};
use crate::core::getset;
use postgres::{Client, NoTls};
use serde_json::json;
// МЕТОДЫ КОМПОНЕНТЫ -------------------------------------------------------------------------------
// Синонимы
pub const METHODS: &[&[u16]] = &[
name!("Connect"),
name!("Close"),
name!("Execute"),
];
// Число параметров функций компоненты
pub fn get_params_amount(num: usize) -> usize {
match num {
0 => 0,
1 => 0,
2 => 3,
_ => 0,
}
}
// Соответствие функций Rust функциям компоненты
// Вызовы должны быть обернуты в Box::new
pub fn cal_func(obj: &mut AddIn, num: usize, params: &mut [Variant]) -> Box<dyn getset::ValueType> {
match num {
0 => Box::new(obj.initialize()),
1 => Box::new(obj.close_connection()),
2 => {
let query = params[0].get_string().unwrap_or("".to_string());
let params_json = params[1].get_string().unwrap_or("".to_string());
let force_result = params[2].get_bool().unwrap_or(false);
Box::new(methods::execute_query(obj, query, params_json, force_result))
},
_ => Box::new(false), // Неверный номер команды
}
}
// -------------------------------------------------------------------------------------------------
// ПОЛЯ КОМПОНЕНТЫ ---------------------------------------------------------------------------------
// Синонимы
pub const PROPS: &[&[u16]] = &[
name!("Database")
];
pub struct AddIn {
connection_string: String,
client: Option<Client>,
}
impl AddIn {
/// Создает новый объект
pub fn new() -> Self {
AddIn {
connection_string: String::new(),
client: None,
}
}
pub fn initialize(&mut self) -> String {
match Client::connect(&self.connection_string, NoTls) {
Ok(client) => {
self.client = Some(client);
json!({"result": true}).to_string()
}
Err(e) => json!({
"result": false,
"error": e.to_string()
})
.to_string(),
}
}
pub fn get_connection(&self) -> Option<&Client> {
self.client.as_ref()
}
pub fn close_connection(&mut self) -> String {
if self.client.take().is_some() {
json!({"result": true}).to_string()
} else {
json!({
"result": false,
"error": "Connection already closed"
})
.to_string()
}
}
pub fn get_field_ptr(&self, index: usize) -> *const dyn getset::ValueType {
match index {
0 => &self.connection_string as &dyn getset::ValueType as *const _,
_ => panic!("Index out of bounds"),
}
}
pub fn get_field_ptr_mut(&mut self, index: usize) -> *mut dyn getset::ValueType { self.get_field_ptr(index) as *mut _ }
}
// -------------------------------------------------------------------------------------------------
// УНИЧТОЖЕНИЕ ОБЪЕКТА -----------------------------------------------------------------------------
// Обработка удаления объекта
impl Drop for AddIn {
fn drop(&mut self) {}
}

View File

@@ -0,0 +1,78 @@
use addin1c::{Variant, Tm};
pub trait ValueType {
fn get_value(&self, val: &mut Variant) -> bool;
fn set_value(&mut self, val: &Variant);
}
// Реализация для i32
impl ValueType for i32 {
fn get_value(&self, val: &mut Variant) -> bool {
val.set_i32(*self);
true
}
fn set_value(&mut self, val: &Variant) {
*self = val.get_i32().unwrap_or(0);
}
}
// Реализация для f64
impl ValueType for f64 {
fn get_value(&self, val: &mut Variant) -> bool {
val.set_f64(*self);
true
}
fn set_value(&mut self, val: &Variant) {
*self = val.get_f64().unwrap_or(0.0);
}
}
// Реализация для bool
impl ValueType for bool {
fn get_value(&self, val: &mut Variant) -> bool {
val.set_bool(*self);
true
}
fn set_value(&mut self, val: &Variant) {
*self = val.get_bool().unwrap_or(false);
}
}
// Реализация для tm
impl ValueType for Tm {
fn get_value(&self, val: &mut Variant) -> bool {
val.set_date(*self);
true
}
fn set_value(&mut self, val: &Variant) {
*self = val.get_date().unwrap_or(Tm::default());
}
}
// Реализация для String
impl ValueType for String {
fn get_value(&self, val: &mut Variant) -> bool {
let s: Vec<u16> = self.encode_utf16().collect();
val.set_str1c(s.as_slice()).is_ok()
}
fn set_value(&mut self, val: &Variant) {
*self = val.get_string().unwrap_or("".to_string());
}
}
// Реализация для Vec<u8>
impl ValueType for Vec<u8> {
fn get_value(&self, val: &mut Variant) -> bool {
val.set_blob(self.as_slice()).is_ok()
}
fn set_value(&mut self, val: &Variant) {
*self = val.get_blob().unwrap_or(&[]).to_vec()
}
}

View File

@@ -0,0 +1,53 @@
pub mod getset;
use addin1c::{name, RawAddin, Variant};
use crate::component::METHODS;
use crate::component::PROPS;
use crate::component::get_params_amount;
use crate::component::cal_func;
use crate::component::AddIn;
// Определение класса
impl RawAddin for AddIn {
fn register_extension_as(&mut self) -> &'static [u16] {
name!("Main")
}
fn get_n_props(&mut self) -> usize {
PROPS.len()
}
fn find_prop(&mut self, name: &[u16]) -> Option<usize> {
PROPS.iter().position(|&x| x == name)
}
fn get_prop_name(&mut self, num: usize, _alias: usize) -> Option<&'static [u16]> { PROPS.get(num).copied() }
fn get_prop_val(&mut self, num: usize, val: &mut Variant) -> bool {let field: &dyn getset::ValueType = &self[num]; field.get_value(val) }
fn set_prop_val(&mut self, num: usize, val: &Variant) -> bool {let field: &mut dyn getset::ValueType = &mut self[num]; field.set_value(val); true }
fn is_prop_readable(&mut self, _num: usize) -> bool { true }
fn is_prop_writable(&mut self, _num: usize) -> bool { true }
fn get_n_methods(&mut self) -> usize { METHODS.len() }
fn find_method(&mut self, name: &[u16]) -> Option<usize> { METHODS.iter().position(|&x| x == name) }
fn get_method_name(&mut self, num: usize, _alias: usize) -> Option<&'static [u16]> { METHODS.get(num).copied() }
fn get_n_params(&mut self, num: usize) -> usize { get_params_amount(num) }
fn get_param_def_value(&mut self, _method_num: usize, _param_num: usize, _value: Variant, ) -> bool { true }
fn has_ret_val(&mut self, _num: usize) -> bool { true }
fn call_as_proc(&mut self, _num: usize, _params: &mut [Variant]) -> bool { false }
fn call_as_func(&mut self, num: usize, params: &mut [Variant], ret_value: &mut Variant, ) -> bool { cal_func(self, num, params).get_value(ret_value) }
}
impl std::ops::Index<usize> for AddIn {
type Output = dyn getset::ValueType;
fn index(&self, index: usize) -> &Self::Output {
unsafe { &*self.get_field_ptr(index) }
}
}
impl std::ops::IndexMut<usize> for AddIn {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
unsafe { &mut *self.get_field_ptr_mut(index) }
}
}

View File

@@ -0,0 +1,49 @@
pub mod component;
mod core;
use std::{
ffi::{c_int, c_long, c_void},
sync::atomic::{AtomicI32, Ordering},
};
use component::AddIn;
use addin1c::{create_component, destroy_component, name, AttachType};
pub static mut PLATFORM_CAPABILITIES: AtomicI32 = AtomicI32::new(-1);
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn GetClassObject(_name: *const u16, component: *mut *mut c_void) -> c_long {
let addin = AddIn::new();
create_component(component, addin)
}
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn DestroyObject(component: *mut *mut c_void) -> c_long {
destroy_component(component)
}
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn GetClassNames() -> *const u16 {
// small strings for performance
name!("Main").as_ptr()
}
#[allow(non_snake_case)]
#[no_mangle]
#[allow(static_mut_refs)]
pub unsafe extern "C" fn SetPlatformCapabilities(capabilities: c_int) -> c_int {
PLATFORM_CAPABILITIES.store(capabilities, Ordering::Relaxed);
3
}
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn GetAttachType() -> AttachType {
AttachType::Any
}