Add quotes for all identifiers in queries

This commit is contained in:
fabianlindfors 2022-01-13 00:34:42 +01:00
parent 0dd8272e9f
commit 9cd9bd395b
10 changed files with 216 additions and 173 deletions

View File

@ -363,13 +363,23 @@ impl Reshape {
let select_columns: Vec<String> = table
.columns
.iter()
.map(|column| format!("{} AS {}", column.real_name, column.name))
.map(|column| {
format!(
r#"
"{real_name}" AS "{alias}"
"#,
real_name = column.real_name,
alias = column.name,
)
})
.collect();
db.run(&format!(
"CREATE OR REPLACE VIEW {schema}.{view_name} AS
r#"
CREATE OR REPLACE VIEW {schema}."{view_name}" AS
SELECT {columns}
FROM {table_name}",
FROM "{table_name}"
"#,
schema = schema,
table_name = table.real_name,
view_name = table.name,
@ -400,8 +410,12 @@ impl Reshape {
// Remove all tables
let schema = Schema::new();
for table in schema.get_tables(&mut self.db)? {
self.db
.run(&format!("DROP TABLE IF EXISTS {} CASCADE", table.real_name))?;
self.db.run(&format!(
r#"
DROP TABLE IF EXISTS "{}" CASCADE
"#,
table.real_name
))?;
}
// Reset state

View File

@ -77,10 +77,10 @@ impl Action for AddColumn {
// Add column as NOT NULL
let query = format!(
"
ALTER TABLE {table}
r#"
ALTER TABLE "{table}"
ADD COLUMN IF NOT EXISTS {definition};
",
"#,
table = self.table,
definition = definition_parts.join(" "),
);
@ -104,7 +104,7 @@ impl Action for AddColumn {
// Add triggers to fill in values as they are inserted/updated
let query = format!(
"
r#"
CREATE OR REPLACE FUNCTION {trigger_name}()
RETURNS TRIGGER AS $$
BEGIN
@ -119,9 +119,9 @@ impl Action for AddColumn {
END
$$ language 'plpgsql';
DROP TRIGGER IF EXISTS {trigger_name} ON {table};
CREATE TRIGGER {trigger_name} BEFORE UPDATE OR INSERT ON {table} FOR EACH ROW EXECUTE PROCEDURE {trigger_name}();
",
DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
CREATE TRIGGER "{trigger_name}" BEFORE UPDATE OR INSERT ON "{table}" FOR EACH ROW EXECUTE PROCEDURE {trigger_name}();
"#,
temp_column_name = temp_column_name,
trigger_name = self.trigger_name(ctx),
up = up,
@ -143,11 +143,11 @@ impl Action for AddColumn {
// Thanks to this, we can set the full column as NOT NULL later with minimal locking.
if !self.column.nullable {
let query = format!(
"
ALTER TABLE {table}
ADD CONSTRAINT {constraint_name}
CHECK ({column} IS NOT NULL) NOT VALID
",
r#"
ALTER TABLE "{table}"
ADD CONSTRAINT "{constraint_name}"
CHECK ("{column}" IS NOT NULL) NOT VALID
"#,
table = self.table,
constraint_name = self.not_null_constraint_name(ctx),
column = temp_column_name,
@ -168,10 +168,10 @@ impl Action for AddColumn {
// Remove triggers and procedures
let query = format!(
"
DROP TRIGGER IF EXISTS {trigger_name} ON {table};
DROP FUNCTION IF EXISTS {trigger_name};
",
r#"
DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
DROP FUNCTION IF EXISTS "{trigger_name}";
"#,
table = self.table,
trigger_name = self.trigger_name(ctx),
);
@ -184,10 +184,10 @@ impl Action for AddColumn {
// Validate the temporary constraint (should always be valid).
// This performs a sequential scan but does not take an exclusive lock.
let query = format!(
"
ALTER TABLE {table}
VALIDATE CONSTRAINT {constraint_name}
",
r#"
ALTER TABLE "{table}"
VALIDATE CONSTRAINT "{constraint_name}"
"#,
table = self.table,
constraint_name = self.not_null_constraint_name(ctx),
);
@ -200,10 +200,10 @@ impl Action for AddColumn {
// the existing constraint for correctness which makes the lock short-lived.
// Source: https://dba.stackexchange.com/a/268128
let query = format!(
"
ALTER TABLE {table}
ALTER COLUMN {column} SET NOT NULL
",
r#"
ALTER TABLE "{table}"
ALTER COLUMN "{column}" SET NOT NULL
"#,
table = self.table,
column = self.temp_column_name(ctx),
);
@ -213,10 +213,10 @@ impl Action for AddColumn {
// Drop the temporary constraint
let query = format!(
"
ALTER TABLE {table}
DROP CONSTRAINT {constraint_name}
",
r#"
ALTER TABLE "{table}"
DROP CONSTRAINT "{constraint_name}"
"#,
table = self.table,
constraint_name = self.not_null_constraint_name(ctx),
);
@ -228,10 +228,10 @@ impl Action for AddColumn {
// Rename the temporary column to its real name
transaction
.run(&format!(
"
ALTER TABLE {table}
RENAME COLUMN {temp_column_name} TO {column_name}
",
r#"
ALTER TABLE "{table}"
RENAME COLUMN "{temp_column_name}" TO "{column_name}"
"#,
table = self.table,
temp_column_name = self.temp_column_name(ctx),
column_name = self.column.name,
@ -252,10 +252,10 @@ impl Action for AddColumn {
fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
// Remove column
let query = format!(
"
ALTER TABLE {table}
DROP COLUMN IF EXISTS {column}
",
r#"
ALTER TABLE "{table}"
DROP COLUMN IF EXISTS "{column}"
"#,
table = self.table,
column = self.temp_column_name(ctx),
);
@ -263,10 +263,10 @@ impl Action for AddColumn {
// Remove triggers and procedures
let query = format!(
"
DROP TRIGGER IF EXISTS {trigger_name} ON {table};
DROP FUNCTION IF EXISTS {trigger_name};
",
r#"
DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
DROP FUNCTION IF EXISTS "{trigger_name}";
"#,
table = self.table,
trigger_name = self.trigger_name(ctx),
);

View File

@ -31,13 +31,13 @@ impl Action for AddIndex {
.columns
.iter()
.filter(|column| self.columns.contains(&column.name))
.map(|column| column.real_name.to_string())
.map(|column| format!("\"{}\"", column.real_name))
.collect();
db.run(&format!(
"
CREATE INDEX CONCURRENTLY {name} ON {table} ({columns})
",
r#"
CREATE INDEX CONCURRENTLY "{name}" ON "{table}" ({columns})
"#,
name = self.name,
table = self.table,
columns = column_real_names.join(", "),
@ -58,9 +58,9 @@ impl Action for AddIndex {
fn abort(&self, _ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
db.run(&format!(
"
DROP INDEX CONCURRENTLY IF EXISTS {name}
",
r#"
DROP INDEX CONCURRENTLY IF EXISTS "{name}"
"#,
name = self.name,
))
.context("failed to drop index")?;

View File

@ -67,14 +67,13 @@ impl Action for AlterColumn {
}
let query = format!(
"
ALTER TABLE {table}
r#"
ALTER TABLE "{table}"
ADD COLUMN IF NOT EXISTS {temp_column_definition}
",
"#,
table = self.table,
temp_column_definition = temp_column_definition_parts.join(" "),
);
println!("Query {}", query);
db.run(&query).context("failed to add temporary column")?;
// If up or down wasn't provided, we default to simply moving the value over.
@ -97,7 +96,7 @@ impl Action for AlterColumn {
.collect();
let query = format!(
"
r#"
CREATE OR REPLACE FUNCTION {up_trigger}()
RETURNS TRIGGER AS $$
BEGIN
@ -113,8 +112,8 @@ impl Action for AlterColumn {
END
$$ language 'plpgsql';
DROP TRIGGER IF EXISTS {up_trigger} ON {table};
CREATE TRIGGER {up_trigger} BEFORE INSERT OR UPDATE ON {table} FOR EACH ROW EXECUTE PROCEDURE {up_trigger}();
DROP TRIGGER IF EXISTS "{up_trigger}" ON "{table}";
CREATE TRIGGER "{up_trigger}" BEFORE INSERT OR UPDATE ON "{table}" FOR EACH ROW EXECUTE PROCEDURE {up_trigger}();
CREATE OR REPLACE FUNCTION {down_trigger}()
RETURNS TRIGGER AS $$
@ -131,19 +130,19 @@ impl Action for AlterColumn {
END
$$ language 'plpgsql';
DROP TRIGGER IF EXISTS {down_trigger} ON {table};
CREATE TRIGGER {down_trigger} BEFORE INSERT OR UPDATE ON {table} FOR EACH ROW EXECUTE PROCEDURE {down_trigger}();
",
existing_column = &self.column,
existing_column_real = column.real_name,
temp_column = self.temporary_column_name(ctx),
up = up,
down = down,
table = self.table,
up_trigger = self.up_trigger_name(ctx),
down_trigger = self.down_trigger_name(ctx),
declarations = declarations.join("\n"),
);
DROP TRIGGER IF EXISTS "{down_trigger}" ON "{table}";
CREATE TRIGGER "{down_trigger}" BEFORE INSERT OR UPDATE ON "{table}" FOR EACH ROW EXECUTE PROCEDURE {down_trigger}();
"#,
existing_column = &self.column,
existing_column_real = column.real_name,
temp_column = self.temporary_column_name(ctx),
up = up,
down = down,
table = self.table,
up_trigger = self.up_trigger_name(ctx),
down_trigger = self.down_trigger_name(ctx),
declarations = declarations.join("\n"),
);
db.run(&query)
.context("failed to create up and down triggers")?;
@ -157,11 +156,11 @@ impl Action for AlterColumn {
// Thanks to this, we can set the full column as NOT NULL later with minimal locking.
if !column.nullable {
let query = format!(
"
ALTER TABLE {table}
ADD CONSTRAINT {constraint_name}
CHECK ({column} IS NOT NULL) NOT VALID
",
r#"
ALTER TABLE "{table}"
ADD CONSTRAINT "{constraint_name}"
CHECK ("{column}" IS NOT NULL) NOT VALID
"#,
table = self.table,
constraint_name = self.not_null_constraint_name(ctx),
column = self.temporary_column_name(ctx),
@ -181,10 +180,10 @@ impl Action for AlterColumn {
if self.can_short_circuit() {
if let Some(new_name) = &self.changes.name {
let query = format!(
"
ALTER TABLE {table}
RENAME COLUMN {existing_name} TO {new_name}
",
r#"
ALTER TABLE "{table}"
RENAME COLUMN "{existing_name}" TO "{new_name}"
"#,
table = self.table,
existing_name = self.column,
new_name = new_name,
@ -210,10 +209,10 @@ impl Action for AlterColumn {
// Validate the temporary constraint (should always be valid).
// This performs a sequential scan but does not take an exclusive lock.
let query = format!(
"
ALTER TABLE {table}
VALIDATE CONSTRAINT {constraint_name}
",
r#"
ALTER TABLE "{table}"
VALIDATE CONSTRAINT "{constraint_name}"
"#,
table = self.table,
constraint_name = self.not_null_constraint_name(ctx),
);
@ -225,10 +224,10 @@ impl Action for AlterColumn {
// the existing constraint for correctness which makes the lock short-lived.
// Source: https://dba.stackexchange.com/a/268128
let query = format!(
"
ALTER TABLE {table}
ALTER COLUMN {column} SET NOT NULL
",
r#"
ALTER TABLE "{table}"
ALTER COLUMN "{column}" SET NOT NULL
"#,
table = self.table,
column = self.temporary_column_name(ctx),
);
@ -236,10 +235,10 @@ impl Action for AlterColumn {
// Drop the temporary constraint
let query = format!(
"
ALTER TABLE {table}
DROP CONSTRAINT {constraint_name}
",
r#"
ALTER TABLE "{table}"
DROP CONSTRAINT "{constraint_name}"
"#,
table = self.table,
constraint_name = self.not_null_constraint_name(ctx),
);
@ -249,19 +248,20 @@ impl Action for AlterColumn {
// Remove old column
let query = format!(
"
ALTER TABLE {} DROP COLUMN {} CASCADE
",
self.table, self.column
r#"
ALTER TABLE "{table}" DROP COLUMN "{column}" CASCADE
"#,
table = self.table,
column = self.column,
);
db.run(&query).context("failed to drop old column")?;
// Rename temporary column
let column_name = self.changes.name.as_deref().unwrap_or(&self.column);
let query = format!(
"
ALTER TABLE {table} RENAME COLUMN {temp_column} TO {name}
",
r#"
ALTER TABLE "{table}" RENAME COLUMN "{temp_column}" TO "{name}"
"#,
table = self.table,
temp_column = self.temporary_column_name(ctx),
name = column_name,
@ -271,13 +271,13 @@ impl Action for AlterColumn {
// Remove triggers and procedures
let query = format!(
"
DROP TRIGGER IF EXISTS {up_trigger} ON {table};
DROP FUNCTION IF EXISTS {up_trigger};
r#"
DROP TRIGGER IF EXISTS "{up_trigger}" ON "{table}";
DROP FUNCTION IF EXISTS "{up_trigger}";
DROP TRIGGER IF EXISTS {down_trigger} ON {table};
DROP FUNCTION IF EXISTS {down_trigger};
",
DROP TRIGGER IF EXISTS "{down_trigger}" ON "{table}";
DROP FUNCTION IF EXISTS "{down_trigger}";
"#,
table = self.table,
up_trigger = self.up_trigger_name(ctx),
down_trigger = self.down_trigger_name(ctx),
@ -313,10 +313,10 @@ impl Action for AlterColumn {
fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
// Drop temporary column
let query = format!(
"
ALTER TABLE {table}
DROP COLUMN IF EXISTS {temp_column};
",
r#"
ALTER TABLE "{table}"
DROP COLUMN IF EXISTS "{temp_column}";
"#,
table = self.table,
temp_column = self.temporary_column_name(ctx),
);
@ -324,13 +324,13 @@ impl Action for AlterColumn {
// Remove triggers and procedures
let query = format!(
"
DROP TRIGGER IF EXISTS {up_trigger} ON {table};
DROP FUNCTION IF EXISTS {up_trigger};
r#"
DROP TRIGGER IF EXISTS "{up_trigger}" ON "{table}";
DROP FUNCTION IF EXISTS "{up_trigger}";
DROP TRIGGER IF EXISTS {down_trigger} ON {table};
DROP FUNCTION IF EXISTS {down_trigger};
",
DROP TRIGGER IF EXISTS "{down_trigger}" ON "{table}";
DROP FUNCTION IF EXISTS "{down_trigger}";
"#,
table = self.table,
up_trigger = self.up_trigger_name(ctx),
down_trigger = self.down_trigger_name(ctx),

View File

@ -87,7 +87,9 @@ pub fn batch_touch_rows(db: &mut dyn Conn, table: &str, column: &str) -> anyhow:
.iter()
.map(|column| {
format!(
"{table}.{column} = rows.{column}",
r#"
"{table}"."{column}" = rows."{column}"
"#,
table = table,
column = column,
)
@ -97,7 +99,7 @@ pub fn batch_touch_rows(db: &mut dyn Conn, table: &str, column: &str) -> anyhow:
let returning_columns = primary_key
.iter()
.map(|column| format!("rows.{}", column))
.map(|column| format!("rows.\"{}\"", column))
.collect::<Vec<String>>()
.join(", ");
@ -113,24 +115,24 @@ pub fn batch_touch_rows(db: &mut dyn Conn, table: &str, column: &str) -> anyhow:
};
let query = format!(
"
WITH rows AS (
SELECT {primary_key_columns}
FROM public.{table}
{cursor_where}
ORDER BY {primary_key_columns}
LIMIT {batch_size}
), update AS (
UPDATE public.{table}
SET {column} = {column}
FROM rows
WHERE {primary_key_where}
RETURNING {returning_columns}
)
SELECT LAST_VALUE(({primary_key_columns})) OVER () AS last_value
FROM update
LIMIT 1
",
r#"
WITH rows AS (
SELECT {primary_key_columns}
FROM public."{table}"
{cursor_where}
ORDER BY {primary_key_columns}
LIMIT {batch_size}
), update AS (
UPDATE public."{table}"
SET "{column}" = "{column}"
FROM rows
WHERE {primary_key_where}
RETURNING {returning_columns}
)
SELECT LAST_VALUE(({primary_key_columns})) OVER () AS last_value
FROM update
LIMIT 1
"#,
table = table,
primary_key_columns = primary_key_columns,
cursor_where = cursor_where,

View File

@ -42,7 +42,7 @@ impl Action for CreateTable {
.columns
.iter()
.map(|column| {
let mut parts = vec![column.name.to_string(), column.data_type.to_string()];
let mut parts = vec![format!("\"{}\"", column.name), column.data_type.to_string()];
if let Some(default) = &column.default {
parts.push("DEFAULT".to_string());
@ -62,24 +62,46 @@ impl Action for CreateTable {
})
.collect();
let primary_key_columns = self.primary_key.join(", ");
let primary_key_columns = self
.primary_key
.iter()
// Add quotes around all column names
.map(|col| format!("\"{}\"", col))
.collect::<Vec<String>>()
.join(", ");
definition_rows.push(format!("PRIMARY KEY ({})", primary_key_columns));
for foreign_key in &self.foreign_keys {
// Add quotes around all column names
let columns: Vec<String> = foreign_key
.columns
.iter()
.map(|col| format!("\"{}\"", col))
.collect();
let referenced_columns: Vec<String> = foreign_key
.referenced_columns
.iter()
.map(|col| format!("\"{}\"", col))
.collect();
definition_rows.push(format!(
"FOREIGN KEY ({columns}) REFERENCES {table} ({referenced_columns})",
columns = foreign_key.columns.join(", "),
r#"
FOREIGN KEY ({columns}) REFERENCES "{table}" ({referenced_columns})
"#,
columns = columns.join(", "),
table = foreign_key.referenced_table,
referenced_columns = foreign_key.referenced_columns.join(", "),
referenced_columns = referenced_columns.join(", "),
));
}
db.run(&format!(
"CREATE TABLE {} (
{}
)",
self.name,
definition_rows.join(",\n"),
r#"
CREATE TABLE "{name}" (
{definition}
)
"#,
name = self.name,
definition = definition_rows.join(",\n"),
))
.context("failed to create table")?;
Ok(())
@ -97,8 +119,13 @@ impl Action for CreateTable {
fn update_schema(&self, _ctx: &MigrationContext, _schema: &mut Schema) {}
fn abort(&self, _ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
db.run(&format!("DROP TABLE IF EXISTS {}", self.name,))
.context("failed to drop table")?;
db.run(&format!(
r#"
DROP TABLE IF EXISTS {name}
"#,
name = self.name,
))
.context("failed to drop table")?;
Ok(())
}

View File

@ -57,7 +57,7 @@ impl Action for RemoveColumn {
.collect();
let query = format!(
"
r#"
CREATE OR REPLACE FUNCTION {trigger_name}()
RETURNS TRIGGER AS $$
BEGIN
@ -72,9 +72,9 @@ impl Action for RemoveColumn {
END
$$ language 'plpgsql';
DROP TRIGGER IF EXISTS {trigger_name} ON {table};
CREATE TRIGGER {trigger_name} BEFORE UPDATE OR INSERT ON {table} FOR EACH ROW EXECUTE PROCEDURE {trigger_name}();
",
DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
CREATE TRIGGER "{trigger_name}" BEFORE UPDATE OR INSERT ON "{table}" FOR EACH ROW EXECUTE PROCEDURE {trigger_name}();
"#,
column_name = self.column,
trigger_name = self.trigger_name(ctx),
down = down,
@ -94,13 +94,13 @@ impl Action for RemoveColumn {
) -> anyhow::Result<Option<Transaction<'a>>> {
// Remove column, function and trigger
let query = format!(
"
ALTER TABLE {table}
DROP COLUMN IF EXISTS {column};
r#"
ALTER TABLE "{table}"
DROP COLUMN IF EXISTS "{column}";
DROP TRIGGER IF EXISTS {trigger_name} ON {table};
DROP FUNCTION IF EXISTS {trigger_name};
",
DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
DROP FUNCTION IF EXISTS "{trigger_name}";
"#,
table = self.table,
column = self.column,
trigger_name = self.trigger_name(ctx),
@ -122,10 +122,10 @@ impl Action for RemoveColumn {
fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
// Remove function and trigger
db.run(&format!(
"
DROP TRIGGER IF EXISTS {trigger_name} ON {table};
DROP FUNCTION IF EXISTS {trigger_name};
",
r#"
DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
DROP FUNCTION IF EXISTS "{trigger_name}";
"#,
table = self.table,
trigger_name = self.trigger_name(ctx),
))

View File

@ -33,10 +33,10 @@ impl Action for RemoveIndex {
db: &'a mut dyn Conn,
) -> anyhow::Result<Option<Transaction<'a>>> {
db.run(&format!(
"
DROP INDEX CONCURRENTLY IF EXISTS {}
",
self.index
r#"
DROP INDEX CONCURRENTLY IF EXISTS "{name}"
"#,
name = self.index
))
.context("failed to drop index")?;

View File

@ -33,9 +33,9 @@ impl Action for RemoveTable {
) -> anyhow::Result<Option<Transaction<'a>>> {
// Remove table
let query = format!(
"
DROP TABLE IF EXISTS {table};
",
r#"
DROP TABLE IF EXISTS "{table}";
"#,
table = self.table,
);
db.run(&query).context("failed to drop table")?;

View File

@ -34,10 +34,10 @@ impl Action for RenameTable {
) -> anyhow::Result<Option<Transaction<'a>>> {
// Rename table
let query = format!(
"
ALTER TABLE IF EXISTS {table}
RENAME TO {new_name}
",
r#"
ALTER TABLE IF EXISTS "{table}"
RENAME TO "{new_name}"
"#,
table = self.table,
new_name = self.new_name,
);