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

PG: DATE, TIME и UUID

This commit is contained in:
Anton Titovets
2025-02-12 10:54:24 +03:00
parent 4e50c7449e
commit 615eb3884a
10 changed files with 170 additions and 81 deletions

View File

@@ -404,6 +404,7 @@ dependencies = [
"chrono",
"postgres",
"serde_json",
"uuid",
]
[[package]]
@@ -504,10 +505,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f"
dependencies = [
"bytes",
"chrono",
"fallible-iterator",
"postgres-protocol",
"serde",
"serde_json",
"uuid",
]
[[package]]
@@ -816,6 +819,12 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14706d2a800ee8ff38c1d3edb873cd616971ea59eb7c0d046bb44ef59b06a1ae"
[[package]]
name = "uuid"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
[[package]]
name = "version_check"
version = "0.9.5"

View File

@@ -15,7 +15,8 @@ opt-level = "z"
[dependencies]
addin1c = "0.5.0"
postgres = { version = "0.19.9", features = ["with-serde_json-1"]}
postgres = { version = "0.19.9", features = ["with-serde_json-1", "with-chrono-0_4", "with-uuid-1"]}
serde_json = "1.0"
base64 = "0.22.1"
chrono = "0.4.39"
uuid = "1.12.1"

View File

@@ -5,6 +5,8 @@ use crate::component::AddIn;
use std::collections::HashMap;
use std::net::IpAddr;
use std::time::{SystemTime, UNIX_EPOCH};
use chrono::{NaiveDate, NaiveTime, Utc};
use uuid::Uuid;
pub fn execute_query(
add_in: &mut AddIn,
@@ -173,6 +175,27 @@ fn process_object(object: &Map<String, Value>) -> Result<Box<dyn ToSql + Sync>,
} else {
Err("Invalid value for JSON/JSONB: must be an object, array, or valid JSON string".to_string())
}
},
"DATE" => {
let value_str = value.as_str().ok_or("Invalid value for DATE")?;
match parse_date(&value_str){
Ok(date) => Ok(Box::new(date.date()) as Box<dyn ToSql + Sync>),
Err(_) => Err("Invalid value for DATA".to_string()),
}
},
"TIME" => {
let value_str = value.as_str().ok_or("Invalid value for DATE")?;
match parse_date(&value_str){
Ok(date) => Ok(Box::new(date.time()) as Box<dyn ToSql + Sync>),
Err(_) => Err("Invalid value for TIME".to_string()),
}
}
"UUID" => {
value
.as_str()
.and_then(|s| s.parse::<Uuid>().ok())
.map(|uuid| Box::new(uuid) as Box<dyn ToSql + Sync>)
.ok_or_else(|| "Invalid value for UUID".to_string())
}
_ => Err(format!("Unsupported type: {}", key)),
}
@@ -188,82 +211,8 @@ fn rows_to_json(rows: Vec<postgres::Row>) -> String {
let column_name = column.name();
let column_type = column.type_().name();
let value = match column_type.to_uppercase().as_str() {
"BOOL" => row.get::<_, Option<bool>>(column_name)
.map(Value::Bool)
.unwrap_or(Value::Null),
"INT2" | "SMALLINT" | "SMALLSERIAL" => row.get::<_, Option<i16>>(column_name)
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"INT4" | "INT" | "SERIAL" => row.get::<_, Option<i32>>(column_name)
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"OID" => row.get::<_, Option<u32>>(column_name)
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"INT8" | "BIGINT" | "BIGSERIAL" => row.get::<_, Option<i64>>(column_name)
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"FLOAT4" | "REAL" => row.get::<_, Option<f32>>(column_name)
.map(|v| match v {
v if v.is_nan() => Value::String("NaN".to_string()),
v if v.is_infinite() => Value::String("Infinity".to_string()),
_ => serde_json::Number::from_f64(v as f64).map(Value::Number).unwrap_or(Value::Null),
})
.unwrap_or(Value::Null),
"FLOAT8" | "DOUBLE PRECISION" => row.get::<_, Option<f64>>(column_name)
.map(|v| match v {
v if v.is_nan() => Value::String("NaN".to_string()),
v if v.is_infinite() => Value::String("Infinity".to_string()),
_ => serde_json::Number::from_f64(v).map(Value::Number).unwrap_or(Value::Null),
})
.unwrap_or(Value::Null),
"CHAR" => {
row.get::<_, Option<i8>>(column_name)
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null)
},
"VARCHAR" | "TEXT" | "BPCHAR" | "CITEXT" | "NAME" | "LTREE" | "LQUERY" | "LTXTQUERY" | "UNKNOWN" => row.get::<_, Option<String>>(column_name)
.map(Value::String)
.unwrap_or(Value::Null),
"BYTEA" => {
let base64_string = row.get::<_, Option<Vec<u8>>>(column_name)
.map(|v| general_purpose::STANDARD.encode(v))
.unwrap_or("Unable to make Base64 string".to_string());
let mut blob_object = serde_json::Map::new();
blob_object.insert("BYTEA".to_string(), Value::String(base64_string)); // Оборачиваем в объект
Value::Object(blob_object)
},
"HSTORE" => row.get::<_, Option<HashMap<String, Option<String>>>>(column_name)
.map(|hstore| {
let mut map = Map::new();
for (k, v) in hstore {
map.insert(k, v.map(Value::String).unwrap_or(Value::Null));
}
Value::Object(map)
})
.unwrap_or(Value::Null),
"TIMESTAMP" | "TIMESTAMP WITH TIME ZONE" | "TIMESTAMPTZ" => row.get::<_, Option<SystemTime>>(column_name)
.map(|time| {
match time.duration_since(SystemTime::UNIX_EPOCH) {
Ok(d) => Value::Number(d.as_secs().into()), // Положительное значение для времени после UNIX_EPOCH
Err(_) => match SystemTime::UNIX_EPOCH.duration_since(time) {
Ok(d) => Value::Number((-(d.as_secs() as i64)).into()), // Отрицательное значение для даты до UNIX_EPOCH
Err(_) => Value::Null, // Это вообще не должно произойти
},
}
})
.unwrap_or(Value::Null),
"INET" => row.get::<_, Option<IpAddr>>(column_name)
.map(|ip| Value::String(ip.to_string()))
.unwrap_or(Value::Null),
"JSON" | "JSONB" => {
row.get::<_, Option<Value>>(column_name)
.unwrap_or(Value::Null)
},
current_type => Value::String(format!("Unsupported type: {}", current_type)),
};
let value = process_sql_value(&column_name, &column_type, &row)
.unwrap_or_else(|e| Value::String(e.to_string()));
row_map.insert(column_name.to_string(), value);
}
@@ -274,6 +223,103 @@ fn rows_to_json(rows: Vec<postgres::Row>) -> String {
json!({ "result": true, "data": result }).to_string()
}
fn process_sql_value(column_name: &str, column_type: &str, row: &postgres::Row) -> Result<Value, postgres::Error> {
let value = match column_type.to_uppercase().as_str() {
"BOOL" => row.try_get::<_, Option<bool>>(column_name)?
.map(Value::Bool)
.unwrap_or(Value::Null),
"INT2" | "SMALLINT" | "SMALLSERIAL" => row.try_get::<_, Option<i16>>(column_name)?
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"INT4" | "INT" | "SERIAL" => row.try_get::<_, Option<i32>>(column_name)?
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"OID" => row.try_get::<_, Option<u32>>(column_name)?
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"INT8" | "BIGINT" | "BIGSERIAL" => row.try_get::<_, Option<i64>>(column_name)?
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null),
"FLOAT4" | "REAL" => row.try_get::<_, Option<f32>>(column_name)?
.map(|v| match v {
v if v.is_nan() => Value::String("NaN".to_string()),
v if v.is_infinite() => Value::String("Infinity".to_string()),
_ => serde_json::Number::from_f64(v as f64).map(Value::Number).unwrap_or(Value::Null),
})
.unwrap_or(Value::Null),
"FLOAT8" | "DOUBLE PRECISION" => row.try_get::<_, Option<f64>>(column_name)?
.map(|v| match v {
v if v.is_nan() => Value::String("NaN".to_string()),
v if v.is_infinite() => Value::String("Infinity".to_string()),
_ => serde_json::Number::from_f64(v).map(Value::Number).unwrap_or(Value::Null),
})
.unwrap_or(Value::Null),
"CHAR" => {
row.try_get::<_, Option<i8>>(column_name)?
.map(|v| Value::Number(v.into()))
.unwrap_or(Value::Null)
},
"VARCHAR" | "TEXT" | "BPCHAR" | "CITEXT" | "NAME" | "LTREE" | "LQUERY" | "LTXTQUERY" | "UNKNOWN" => row.try_get::<_, Option<String>>(column_name)?
.map(Value::String)
.unwrap_or(Value::Null),
"BYTEA" => {
let base64_string = row.try_get::<_, Option<Vec<u8>>>(column_name)?
.map(|v| general_purpose::STANDARD.encode(v))
.unwrap_or("Unable to make Base64 string".to_string());
let mut blob_object = serde_json::Map::new();
blob_object.insert("BYTEA".to_string(), Value::String(base64_string)); // Оборачиваем в объект
Value::Object(blob_object)
},
"HSTORE" => row.try_get::<_, Option<HashMap<String, Option<String>>>>(column_name)?
.map(|hstore| {
let mut map = Map::new();
for (k, v) in hstore {
map.insert(k, v.map(Value::String).unwrap_or(Value::Null));
}
Value::Object(map)
})
.unwrap_or(Value::Null),
"TIMESTAMP" | "TIMESTAMP WITH TIME ZONE" | "TIMESTAMPTZ" => row.try_get::<_, Option<SystemTime>>(column_name)?
.map(|time| {
match time.duration_since(SystemTime::UNIX_EPOCH) {
Ok(d) => Value::Number(d.as_secs().into()), // Положительное значение для времени после UNIX_EPOCH
Err(_) => match SystemTime::UNIX_EPOCH.duration_since(time) {
Ok(d) => Value::Number((-(d.as_secs() as i64)).into()), // Отрицательное значение для даты до UNIX_EPOCH
Err(_) => Value::Null, // Это вообще не должно произойти
},
}
})
.unwrap_or(Value::Null),
"INET" => row.try_get::<_, Option<IpAddr>>(column_name)?
.map(|ip| Value::String(ip.to_string()))
.unwrap_or(Value::Null),
"DATE" => row.try_get::<_, Option<NaiveDate>>(column_name)?
.map(|date| Value::String(date.to_string()))
.unwrap_or(Value::Null),
"TIME" => row.try_get::<_, Option<NaiveTime>>(column_name)?
.map(|date| Value::String(date.to_string()))
.unwrap_or(Value::Null),
"JSON" | "JSONB" => {
row.try_get::<_, Option<Value>>(column_name)?
.unwrap_or(Value::Null)
},
"UUID" => {
row.try_get::<_, Option<Uuid>>(column_name)?
.map(|uuid| Value::String(uuid.to_string()))
.unwrap_or(Value::Null)
}
current_type => {
match row.try_get::<_, Option<String>>(column_name){
Ok(v) => v.map(Value::String).unwrap_or(Value::Null),
Err(_) => Value::String(format!("Unsupported type: {}", current_type)),
}
},
};
Ok(value)
}
fn format_json_error(error: &str) -> String {
json!({
"result": false,
@@ -281,3 +327,22 @@ fn format_json_error(error: &str) -> String {
})
.to_string()
}
fn parse_date(input: &str) -> Result<chrono::NaiveDateTime, String> {
// Попробуем спарсить полный формат с датой, временем и часовым поясом
if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(input) {
return Ok(datetime.with_timezone(&Utc).naive_local());
}
// Если не получилось, попробуем спарсить только дату и время без часового пояса
if let Ok(naive_datetime) = chrono::NaiveDateTime::parse_from_str(input, "%Y-%m-%dT%H:%M:%S") {
return Ok(naive_datetime);
}
// Если не получилось, попробуем спарсить только дату
if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(input, "%Y-%m-%d") {
Ok(naive_date)
}else{
Err("Invalid ISO 8601 format".to_string())
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -540,7 +540,7 @@
ИначеЕсли ТипЗнч(ТекущийПараметр) = Тип("Дата") Тогда
ТекущийПараметр = Формат(ТекущийПараметр, "ДФ='yyyy-MM-dd HH:MM:ss'");
ТекущийПараметр = XMLСтрока(ТекущийПараметр);
ИначеЕсли ТипЗнч(ТекущийПараметр) = Тип("Структура") Или ТипЗнч(ТекущийПараметр) = Тип("Соответствие") Тогда
@@ -622,6 +622,7 @@
СоответствиеТипов.Вставить("LQUERY" , ОписаниеString);
СоответствиеТипов.Вставить("LTXTQUERY" , ОписаниеString);
СоответствиеТипов.Вставить("INET" , ОписаниеString);
СоответствиеТипов.Вставить("UUID" , ОписаниеString);
Возврат СоответствиеТипов;

View File

@@ -17372,6 +17372,9 @@
СтруктураКолонок.Вставить("ip_field" , "INET");
СтруктураКолонок.Вставить("json_field" , "JSON");
СтруктураКолонок.Вставить("jsonb_field" , "JSONB");
СтруктураКолонок.Вставить("date_field" , "DATE");
СтруктураКолонок.Вставить("time_field" , "TIME");
СтруктураКолонок.Вставить("uuid_field" , "UUID");
Результат = OPI_PostgreSQL.СоздатьТаблицу(Таблица, СтруктураКолонок, СтрокаПодключения);
@@ -17417,6 +17420,7 @@
OPI_ПреобразованиеТипов.ПолучитьДвоичныеДанные(Картинка); // Картинка - Тип: ДвоичныеДанные
СлучайнаяСтруктура = Новый Структура("key,value", "ItsKey", 10);
ТекущаяДата = OPI_Инструменты.ПолучитьТекущуюДату();
СтруктураЗаписи = Новый Структура;
СтруктураЗаписи.Вставить("bool_field" , Новый Структура("BOOL" , Истина));
@@ -17440,6 +17444,9 @@
СтруктураЗаписи.Вставить("ip_field" , Новый Структура("INET" , "127.0.0.1"));
СтруктураЗаписи.Вставить("json_field" , Новый Структура("JSON" , СлучайнаяСтруктура));
СтруктураЗаписи.Вставить("jsonb_field" , Новый Структура("JSONB" , СлучайнаяСтруктура));
СтруктураЗаписи.Вставить("date_field" , Новый Структура("DATE" , ТекущаяДата));
СтруктураЗаписи.Вставить("time_field" , Новый Структура("TIME" , ТекущаяДата));
СтруктураЗаписи.Вставить("uuid_field" , Новый Структура("UUID" , Новый УникальныйИдентификатор()));
Результат = OPI_PostgreSQL.ДобавитьЗаписи(Таблица, СтруктураЗаписи, Ложь, СтрокаПодключения);
@@ -17483,17 +17490,23 @@
Поля.Добавить("ip_field");
Поля.Добавить("json_field");
Поля.Добавить("jsonb_field");
Поля.Добавить("date_field");
Поля.Добавить("time_field");
Поля.Добавить("uuid_field");
Результат = OPI_PostgreSQL.ПолучитьЗаписи(Таблица, Поля, , , , СтрокаПодключения);
// END
Результат["data"][0]["bytea_field"]["BYTEA"] = Лев(Результат["data"][0]["bytea_field"]["BYTEA"], 10) + "...";
Если ЗначениеЗаполнено(Результат["data"]) Тогда
Результат["data"][0]["bytea_field"]["BYTEA"] = Лев(Результат["data"][0]["bytea_field"]["BYTEA"], 10) + "...";
КонецЕсли;
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "ДобавитьЗаписи", "PostgreSQL");
OPI_ПолучениеДанныхТестов.Проверка_РезультатИстина(Результат);
КонецПроцедуры
#КонецОбласти
#КонецОбласти