You've already forked OpenIntegrations
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:
9
src/addins/postgres/Cargo.lock
generated
9
src/addins/postgres/Cargo.lock
generated
@@ -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"
|
||||
|
@@ -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"
|
Binary file not shown.
@@ -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())
|
||||
}
|
||||
}
|
||||
|
BIN
src/en/OInt/addins/OPI_PostgreSQL.zip
vendored
BIN
src/en/OInt/addins/OPI_PostgreSQL.zip
vendored
Binary file not shown.
Binary file not shown.
BIN
src/ru/OInt/addins/OPI_PostgreSQL.zip
vendored
BIN
src/ru/OInt/addins/OPI_PostgreSQL.zip
vendored
Binary file not shown.
@@ -540,7 +540,7 @@
|
||||
|
||||
ИначеЕсли ТипЗнч(ТекущийПараметр) = Тип("Дата") Тогда
|
||||
|
||||
ТекущийПараметр = Формат(ТекущийПараметр, "ДФ='yyyy-MM-dd HH:MM:ss'");
|
||||
ТекущийПараметр = XMLСтрока(ТекущийПараметр);
|
||||
|
||||
ИначеЕсли ТипЗнч(ТекущийПараметр) = Тип("Структура") Или ТипЗнч(ТекущийПараметр) = Тип("Соответствие") Тогда
|
||||
|
||||
@@ -622,6 +622,7 @@
|
||||
СоответствиеТипов.Вставить("LQUERY" , ОписаниеString);
|
||||
СоответствиеТипов.Вставить("LTXTQUERY" , ОписаниеString);
|
||||
СоответствиеТипов.Вставить("INET" , ОписаниеString);
|
||||
СоответствиеТипов.Вставить("UUID" , ОписаниеString);
|
||||
|
||||
Возврат СоответствиеТипов;
|
||||
|
||||
|
@@ -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_ПолучениеДанныхТестов.Проверка_РезультатИстина(Результат);
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#КонецОбласти
|
||||
|
Binary file not shown.
Reference in New Issue
Block a user