use crate::SqlRenderer;
pub const SID_PREREQUISITE_STATEMENT_DELIMITER: &str =
"-- ──── RAZERI SID PREREQUISITE STATEMENT ────";
pub const SID_PROBE_INPUT: &str = "test";
pub const RAZERI_HELPER_SCHEMA: &str = "razeri";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SidPrerequisiteHome {
Ambient,
Schema(&'static str),
}
impl SidPrerequisiteHome {
#[must_use]
pub const fn schema(self) -> Option<&'static str> {
match self {
Self::Ambient => None,
Self::Schema(schema) => Some(schema),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SidPrerequisiteKind {
None,
SqlInstallable,
OperatorArtifact,
Refused,
EmittedInSchema,
Undeclared,
}
#[must_use]
pub fn statements_in_prerequisite_script(script: &str) -> Vec<String> {
if !script.contains(SID_PREREQUISITE_STATEMENT_DELIMITER) {
return Vec::new();
}
script
.split(SID_PREREQUISITE_STATEMENT_DELIMITER)
.filter_map(|segment| {
let statement = segment
.lines()
.skip_while(|line| {
let line = line.trim_start();
line.is_empty() || line.starts_with("--")
})
.collect::<Vec<_>>()
.join("\n");
let statement = statement.trim_end().strip_suffix(';').map_or_else(
|| statement.trim_end().to_owned(),
|trimmed| trimmed.trim_end().to_owned(),
);
(!statement.is_empty()).then_some(statement)
})
.collect()
}
#[must_use]
pub fn kind_of_prerequisite_script(script: &str) -> SidPrerequisiteKind {
if script.trim().is_empty() {
SidPrerequisiteKind::Undeclared
} else if statements_in_prerequisite_script(script).is_empty() {
SidPrerequisiteKind::OperatorArtifact
} else {
SidPrerequisiteKind::SqlInstallable
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SidExternalArtifact {
pub language: String,
pub object_file: String,
pub link_symbol: String,
pub comment: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SidLibraryLoader {
Client,
Server,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SidLibrarySource {
pub file_name: String,
pub source: String,
pub library: String,
pub entry_point: String,
pub build: String,
pub loaded_by: SidLibraryLoader,
}
pub trait ReplicationRenderer: SqlRenderer {
fn session_prerequisites(&self) -> Vec<String> {
Vec::new()
}
fn sid_external_artifact(&self) -> Option<SidExternalArtifact> {
None
}
fn sid_library_source(&self) -> Option<SidLibrarySource> {
None
}
fn sid_prerequisites(&self) -> String {
String::new()
}
fn sid_prerequisite_statements(&self) -> Vec<String> {
statements_in_prerequisite_script(&self.sid_prerequisites())
}
fn sid_pure_sql_fallback(&self) -> Option<String> {
None
}
fn sid_pure_sql_fallback_in(&self, schemas: &[&str]) -> Option<String> {
let _ = schemas;
self.sid_pure_sql_fallback()
}
fn sid_prerequisite_kind(&self) -> SidPrerequisiteKind {
kind_of_prerequisite_script(&self.sid_prerequisites())
}
fn sid_prerequisite_home(&self) -> SidPrerequisiteHome {
SidPrerequisiteHome::Ambient
}
fn sid_validation_query(&self, schema: Option<&str>, null_sid: i64) -> String {
let qualified = match schema {
Some(s) => format!("{s}.generate_sid"),
None => "generate_sid".to_string(),
};
format!("SELECT {qualified}('test') AS test_sid, {null_sid} AS null_sid;\n")
}
fn render_data_extract(
&self,
qualified: &str,
columns: &str,
file_path: &str,
format: &str,
where_clause: &str,
_batch_size: Option<usize>,
) -> String {
let _ = file_path;
let _ = format;
if where_clause.is_empty() {
format!("SELECT {columns} FROM {qualified};\n")
} else {
format!("SELECT {columns} FROM {qualified} {where_clause};\n")
}
}
fn render_data_load(&self, qualified: &str, file_path: &str, _format: &str) -> String {
format!("-- COPY INTO {qualified} FROM '{file_path}';\n")
}
fn render_checksum_query(&self, qualified: &str) -> String {
format!("SELECT COUNT(*) AS cnt FROM {qualified};\n")
}
fn parquet_export_warning(&self) -> Option<&str> {
None
}
fn sid_deployment_warning(&self) -> Option<&str> {
None
}
fn replication_target_warnings(&self) -> Vec<String> {
Vec::new()
}
fn truncate_caveat(&self) -> Option<&str> {
None
}
}
#[cfg(test)]
mod tests {
use super::{ReplicationRenderer, SID_PREREQUISITE_STATEMENT_DELIMITER};
use crate::agnostic::AnsiDialect;
struct Emitting(String);
impl crate::SqlRenderer for Emitting {
fn dialect_name(&self) -> &'static str {
"emitting"
}
fn render_data_type(&self, dt: &crate::DataType) -> String {
AnsiDialect.render_data_type(dt)
}
fn quote_identifier(&self, name: &str) -> String {
AnsiDialect.quote_identifier(name)
}
fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
AnsiDialect.qualify_name(schema, name)
}
fn render_current_timestamp(&self) -> String {
AnsiDialect.render_current_timestamp()
}
}
impl crate::DdlRenderer for Emitting {}
impl crate::FormulaRenderer for Emitting {}
impl ReplicationRenderer for Emitting {
fn sid_prerequisites(&self) -> String {
self.0.clone()
}
}
#[test]
fn the_delimiter_recovers_statements_a_semicolon_split_would_fracture() {
let script = format!(
"-- prose the operator reads\n\
{SID_PREREQUISITE_STATEMENT_DELIMITER}\n\
-- why this one exists\n\
CREATE FUNCTION f() AS $$\n var a = 1; var b = 2; return a + b;\n$$;\n\
{SID_PREREQUISITE_STATEMENT_DELIMITER}\n\
CREATE FUNCTION g() AS $$ 1 $$;\n"
);
let statements = Emitting(script).sid_prerequisite_statements();
assert_eq!(statements.len(), 2, "{statements:#?}");
assert!(statements[0].starts_with("CREATE FUNCTION f()"), "{:?}", statements[0]);
assert!(statements[0].contains("var a = 1; var b = 2; return a + b;"));
assert_eq!(statements[1], "CREATE FUNCTION g() AS $$ 1 $$");
}
#[test]
fn one_trailing_terminator_is_dropped_and_the_bodys_own_semicolons_survive() {
let script = format!(
"{SID_PREREQUISITE_STATEMENT_DELIMITER}\n\
CREATE FUNCTION f() AS $$\n return 1;\n$$;\n\
{SID_PREREQUISITE_STATEMENT_DELIMITER}\n\
CREATE FUNCTION g() AS $$ 1 $$\n"
);
let statements = Emitting(script).sid_prerequisite_statements();
assert_eq!(statements[0], "CREATE FUNCTION f() AS $$\n return 1;\n$$");
assert!(statements[0].contains("return 1;"), "the body's own `;` is not a terminator");
assert_eq!(
statements[1], "CREATE FUNCTION g() AS $$ 1 $$",
"a statement with no terminator is unchanged"
);
}
#[test]
fn an_undelimited_script_declares_no_executable_statements() {
let operator_recipe = "-- compile this yourself:\n/* int main() { } */\n".to_owned();
assert!(Emitting(operator_recipe).sid_prerequisite_statements().is_empty());
assert!(AnsiDialect.sid_prerequisite_statements().is_empty());
}
}