//extern crate hyper;
//extern crate hyper_rustls;
//extern crate hyper_util;
extern crate google_sheets4 as sheets4;
use sheets4::{Sheets, hyper_rustls, hyper_util, yup_oauth2};
use serde_json::Value;
use std::collections::HashMap;
use futures::future::join_all;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_rustls::HttpsConnector;
use inflector::Inflector; // 패키지: id = "0.11" (네이밍 변환용)
pub type SheetsHub = Sheets>;
//#[derive(Default, Debug, Clone, PartialEq)]
pub struct SpreadsheetExporter {
hub: SheetsHub,
spreadsheet_id: String,
}
impl SpreadsheetExporter {
pub fn new(hub: SheetsHub, spreadsheet_id: String) -> Self {
Self { hub, spreadsheet_id }
}
// Value 타입을 String으로 안전하게 변환하는 헬퍼 함수
fn val_to_string(val: &Value) -> String {
match val {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
_ => val.to_string().replace('"', ""),
}
}
// 시트에서 가져온 Vec>를 정제된 HashMap 리스트로 변환
fn clean_rows(&self, values: &Vec>) -> Vec> {
if values.is_empty() { return vec![]; }
let headers: Vec = values[0].iter().map(Self::val_to_string).collect();
let mut result = Vec::new();
for row in values.iter().skip(1) {
if row.is_empty() || Self::val_to_string(&row[0]).trim().is_empty() { break; }
let mut row_map = HashMap::new();
for (i, header) in headers.iter().enumerate() {
let val = row.get(i).map(Self::val_to_string).unwrap_or_default();
row_map.insert(header.clone(), val);
}
result.push(row_map);
}
result
}
fn split_column(&self, raw_text: &str) -> String {
raw_text.split(',').map(|s| format!("`{}`", s.trim())).collect::>().join(", ")
}
fn format_sql_value(&self, val: &str) -> String {
let t = val.trim();
if t.is_empty() || t.to_uppercase() == "NULL" { return "NULL".to_string(); }
if t.parse::().is_ok() { t.to_string() } else { format!("'{}'", t.replace("'", "''")) }
}
async fn export_schema_unit(&self, row: HashMap) -> String {
let schema = row.get("SCHEMA").cloned().unwrap_or_default();
let table_name = row.get("TABLE_NAME").cloned().unwrap_or_default();
let engine = row.get("ENGINE").cloned().unwrap_or_default();
let auto_increment = row.get("AUTO_INCREMENT").cloned().unwrap_or_default();
let charset = row.get("CHARSET").cloned().unwrap_or_default();
let collate = row.get("COLLATE").cloned().unwrap_or_default();
let ss_id = match row.get("SPREADSHEET_ID") {
Some(id) => id,
None => return format!("-- Error: Missing ID for {}", table_name),
};
let result = self.hub.spreadsheets().values_batch_get(ss_id)
.add_ranges("COLUMN!A:Z")
.add_ranges("INDEX!A:Z")
.add_ranges("FOREIGN_KEY!A:Z")
.doit().await;
let (_, response) = match result {
Ok(res) => res,
Err(e) => return format!("-- API Error in {}: {}\n", table_name, e),
};
let v_ranges = response.value_ranges.unwrap_or_default();
let mut all_defs = Vec::new();
// 1. COLUMN 처리
if let Some(vals) = v_ranges.get(0).and_then(|r| r.values.as_ref()) {
for r in self.clean_rows(vals) {
let f = r.get("FIELD").cloned().unwrap_or_default();
let t = r.get("TYPE").cloned().unwrap_or_default();
let s = r.get("SIGNED").cloned().unwrap_or_default();
let l = r.get("LENGTH").filter(|s| !s.is_empty()).map_or("".to_string(), |v| format!("({})", v));
let n = if ["NOT", "NO", "FALSE", "0"].contains(&r.get("NULL").unwrap_or(&"".into()).to_uppercase().as_str()) { "NOT NULL" } else { "" };
let d = r.get("DEFAULT").filter(|s| !s.is_empty()).map_or("".to_string(), |v| format!("DEFAULT '{}'", v.replace("'", "''")));
let c = r.get("COMMENT").filter(|s| !s.is_empty()).map_or("".to_string(), |v| format!("COMMENT '{}'", v.replace("'", "''")));
all_defs.push(format!(" `{}` {} {}{} {} {} {} {}", f.to_snake_case(), t, s, l, n, d, r.get("EXTRA").unwrap_or(&"".into()), c).trim().to_string());
}
}
// 2. INDEX 처리
if let Some(vals) = v_ranges.get(1).and_then(|r| r.values.as_ref()) {
for r in self.clean_rows(vals) {
let itype = r.get("TYPE").unwrap_or(&"".into()).to_uppercase();
let cols = self.split_column(r.get("COLUMN").unwrap_or(&"".into()));
if itype == "PRIMARY" { all_defs.push(format!("PRIMARY KEY ({})", cols.to_snake_case())); }
else {
let prefix = if itype == "KEY" { "".into() } else { format!("{} ", itype) };
all_defs.push(format!("{}KEY `{}` ({})", prefix, r.get("NAME").unwrap_or(&"".into()).to_snake_case(), cols));
}
}
}
// 3. FOREIGN KEY 처리
if let Some(vals) = v_ranges.get(2).and_then(|r| r.values.as_ref()) {
for r in self.clean_rows(vals) {
let mut fk = format!("CONSTRAINT `{}` FOREIGN KEY (`{}`) REFERENCES `{}` (`{}`)",
r.get("NAME").unwrap_or(&"".to_string()).to_snake_case(),
r.get("COLUMN").unwrap_or(&"".to_string()).to_snake_case(),
r.get("REF_TABLE").unwrap_or(&"".to_string()).to_plural().to_snake_case(),
r.get("REF_COLUMN").unwrap_or(&"".to_string()).to_snake_case());
if let Some(u) = r.get("ON_UPDATE").filter(|s| !s.is_empty()) { fk.push_str(&format!(" ON UPDATE {}", u)); }
if let Some(d) = r.get("ON_DELETE").filter(|s| !s.is_empty()) { fk.push_str(&format!(" ON DELETE {}", d)); }
all_defs.push(fk);
}
}
let schema_name = schema.to_snake_case();
let table_name = table_name.to_plural().to_snake_case();
format!("CREATE DATABASE IF NOT EXISTS {};\nUSE {};\nDROP TABLE IF EXISTS `{}`;\nCREATE TABLE `{}` (\n{}\n) ENGINE={} AUTO_INCREMENT={} DEFAULT CHARSET={} COLLATE={};\n",
schema_name, schema_name, table_name, table_name, all_defs.join(",\n"), engine, auto_increment, charset, collate)
}
async fn export_data_unit(&self, row: HashMap) -> String {
let table_name = row.get("TABLE_NAME").cloned().unwrap_or_default();
let ss_id = row.get("SPREADSHEET_ID").expect("No ID");
let result = self.hub.spreadsheets().values_get(ss_id, "DATA!A:Z").doit().await;
let (_, response) = match result {
Ok(res) => res,
Err(_) => return "".into(),
};
let values = response.values.unwrap_or_default();
if values.len() <= 1 { return "".into(); }
let headers: Vec = values[0].iter().map(Self::val_to_string).collect();
let mut insert_rows = Vec::new();
for r in self.clean_rows(&values) {
let row_vals: Vec = headers.iter()
.map(|h| self.format_sql_value(r.get(h).unwrap_or(&"".into())))
.collect();
insert_rows.push(format!("({})", row_vals.join(", ")));
}
let h_sql = headers.iter().map(|h| format!("`{}`", h.to_snake_case())).collect::>().join(", ");
format!("INSERT INTO `{}` ({})\nVALUES\n{};\n", table_name.to_plural(), h_sql, insert_rows.join(",\n"))
}
async fn execute_parallel(&self, unit_func: F) -> String
where
F: Fn(HashMap) -> Fut,
Fut: std::future::Future
본 공간은 인공지능(AI)이 도출한 방대한 지식을 일목요연하게 큐레이션하여 기록하는 블로그입니다. 기술적 한계로 인해 모든 정보의 완벽한 정확성을 보장하기는 어려우므로, 최종적인 판단과 책임은 독자 본인에게 있음을 정중히 안내해 드립니다.
2026년 7월 21일 화요일
src/gspread.rs
database.py
import os from contextlib import contextmanager # contextmanager 임포트 from typing import Generator from dotenv...
-
bool atob(const char * string) { if (!strcmp(string, "true")) return true; return false; }
-
/// CXXXView.cpp void CXXXView::OnInitialUpdate() { CView::OnInitialUpdate(); // TODO: Add your specialized code here and/or call the ...
-
WxWidgets: http://www.wxwidgets.org/downloads/ MinGW: http://sourceforge.net/projects/mingw/files/ * Microsoft Windows Environment Var...