1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use crate::api::places_api::SyncConn;
use crate::error::*;
use rusqlite::named_params;
use types::Timestamp;
use url::Url;
lazy_static::lazy_static! {
pub static ref NOW: Timestamp = Timestamp::now();
}
pub mod sql_fns {
use crate::import::common::NOW;
use crate::storage::URL_LENGTH_MAX;
use rusqlite::{functions::Context, types::ValueRef, Result};
use std::convert::TryFrom;
use types::Timestamp;
use url::Url;
#[inline(never)]
pub fn sanitize_timestamp(ctx: &Context<'_>) -> Result<Timestamp> {
let now = *NOW;
let is_sane = |ts: Timestamp| -> bool { Timestamp::EARLIEST <= ts && ts <= now };
if let Ok(ts) = ctx.get::<i64>(0) {
let ts = Timestamp(u64::try_from(ts).unwrap_or(0));
if is_sane(ts) {
return Ok(ts);
}
let ts = Timestamp(ts.as_millis() / 1000);
if is_sane(ts) {
return Ok(ts);
}
}
Ok(now)
}
#[inline(never)]
pub fn validate_url(ctx: &Context<'_>) -> Result<Option<String>> {
let val = ctx.get_raw(0);
let href = if let ValueRef::Text(s) = val {
String::from_utf8_lossy(s).to_string()
} else {
return Ok(None);
};
if href.len() > URL_LENGTH_MAX {
return Ok(None);
}
if let Ok(url) = Url::parse(&href) {
Ok(Some(url.into_string()))
} else {
Ok(None)
}
}
#[inline(never)]
pub fn sanitize_utf8(ctx: &Context<'_>) -> Result<Option<String>> {
let val = ctx.get_raw(0);
Ok(match val {
ValueRef::Text(s) => Some(String::from_utf8_lossy(s).to_string()),
ValueRef::Null => None,
_ => Some("".to_owned()),
})
}
}
pub fn attached_database<'a>(
conn: &'a SyncConn<'a>,
path: &Url,
db_alias: &'static str,
) -> Result<ExecuteOnDrop<'a>> {
conn.execute_named(
"ATTACH DATABASE :path AS :db_alias",
named_params! {
":path": path.as_str(),
":db_alias": db_alias,
},
)?;
Ok(ExecuteOnDrop {
conn,
sql: format!("DETACH DATABASE {};", db_alias),
})
}
pub struct ExecuteOnDrop<'a> {
conn: &'a SyncConn<'a>,
sql: String,
}
impl<'a> ExecuteOnDrop<'a> {
pub fn new(conn: &'a SyncConn<'a>, sql: String) -> Self {
Self { conn, sql }
}
pub fn execute_now(self) -> Result<()> {
self.conn.execute_batch(&self.sql)?;
std::mem::forget(self);
Ok(())
}
}
impl Drop for ExecuteOnDrop<'_> {
fn drop(&mut self) {
if let Err(e) = self.conn.execute_batch(&self.sql) {
log::error!("Failed to clean up after import! {}", e);
log::debug!(" Failed query: {}", &self.sql);
}
}
}