///|
// Portable literal subset of the pinned ETL default parser. SQL expressions
// are never copied into ClickHouse; decoded literals are quoted in its dialect.
fn ch_literal_end(value : String, start : Int) -> Int? {
if start >= value.length() || value[start] != '\'' {
return None
}
let mut i = start + 1
while i < value.length() {
if value[i] == '\'' {
if i + 1 < value.length() && value[i + 1] == '\'' {
i += 2
} else {
return Some(i + 1)
}
} else {
i += 1
}
}
None
}
///|
fn ch_normalize_default(input : String) -> String {
let mut text = input.trim().to_owned()
for ;; {
let before = text
let mut depth = 0
let mut binary = false
let mut i = 0
while i < text.length() {
let ch = text[i]
if ch == '\'' {
i = ch_literal_end(text, i).unwrap_or(text.length())
continue
}
if ch == '(' {
depth += 1
}
if ch == ')' {
depth = Int::max(depth - 1, 0)
}
if depth == 0 &&
(
ch == '*' ||
ch == '/' ||
ch == '%' ||
(i > 0 && (ch == '+' || ch == '-')) ||
(ch == '|' && i + 1 < text.length() && text[i + 1] == '|')
) {
binary = true
}
if depth == 0 && ch == ':' && i + 1 < text.length() && text[i + 1] == ':' {
let suffix = text[i + 2:].trim()
if !binary &&
!suffix.is_empty() &&
suffix
.iter()
.all(c => {
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
"_ \".[](),".contains(c.to_string())
}) {
text = text[:i].trim().to_owned()
}
break
}
i += 1
}
if text.has_prefix("(") && text.has_suffix(")") {
depth = 0
i = 0
let mut outer = true
while i < text.length() {
let ch = text[i]
if ch == '\'' {
i = ch_literal_end(text, i).unwrap_or(text.length())
continue
}
if ch == '(' {
depth += 1
}
if ch == ')' {
depth -= 1
if depth < 0 || (depth == 0 && i != text.length() - 1) {
outer = false
break
}
}
i += 1
}
if outer && depth == 0 {
text = text[1:text.length() - 1].trim().to_owned()
}
}
if text == before {
return text
}
}
}
///|
fn ch_numeric_literal(value : String) -> Bool {
lexmatch value {
re"^(\+|-)?([0-9]+(\.[0-9]*)?|\.[0-9]+)$" => true
_ => false
}
}
///|
fn ch_default_expression(input : String, oid : UInt) -> String? {
let value = ch_normalize_default(input)
let lower = value.to_lower()
if value.is_empty() ||
lower == "null" ||
lower.has_prefix("nextval(") ||
lower.contains("select ") ||
value.contains("::") ||
lower.has_prefix("array[") ||
lower.has_prefix("array ") {
return None
}
let text_type = oid == 18U ||
oid == 19U ||
oid == 25U ||
oid == 1042U ||
oid == 1043U
let numeric_type = oid == 21U ||
oid == 23U ||
oid == 20U ||
oid == 700U ||
oid == 701U ||
oid == 26U
if ch_literal_end(value, 0) == Some(value.length()) {
let decoded = value[1:value.length() - 1]
.to_owned()
.replace_all(old="''", new="'")
if oid == 16U {
return if decoded.to_lower() == "true" || decoded.to_lower() == "false" {
Some(decoded)
} else {
None
}
}
if numeric_type || oid == 1700U {
if !ch_numeric_literal(decoded) {
return None
}
return Some(if oid == 1700U { ch_quote(decoded, '\'') } else { decoded })
}
let literal = ch_quote(decoded, '\'')
match oid {
1082U => Some("toDate32(" + literal + ")")
1114U => Some("toDateTime64(" + literal + ", 6, 'UTC')")
1184U => Some("parseDateTime64BestEffort(" + literal + ", 6, 'UTC')")
1083U | 1266U | 1186U | 114U | 3802U | 790U | 2950U => Some(literal)
_ => if text_type { Some(literal) } else { None }
}
} else if ch_numeric_literal(value) {
if numeric_type {
Some(value)
} else if text_type || oid == 1700U || oid == 790U {
Some(ch_quote(value, '\''))
} else {
None
}
} else if lower == "true" || lower == "false" {
if oid == 16U {
Some(value)
} else if text_type {
Some(ch_quote(value, '\''))
} else {
None
}
} else {
None
}
}
///|
fn ch_default_clause(column : SchemaColumn) -> String {
match column.default_expression {
Some(expression) =>
match ch_default_expression(expression, column.type_oid) {
Some(value) => " DEFAULT " + value
None => ""
}
None => ""
}
}