mirror of
https://github.com/trailbaseio/trailbase.git
synced 2026-05-19 07:49:57 -05:00
Fix migration filename and named placeholder construction to work for any unicode character.
Our migration library doesn't seem to work with whitespaces.
This commit is contained in:
@@ -250,7 +250,10 @@ function TableSplitView(props: {
|
||||
const selectedTable = createMemo(() => {
|
||||
const filteredTables = filteredTablesAndViews();
|
||||
// useParams returns undefined as a string.
|
||||
const table = params.table === "undefined" ? undefined : params.table;
|
||||
const table =
|
||||
params.table === "undefined"
|
||||
? undefined
|
||||
: decodeURIComponent(params.table);
|
||||
return pickInitiallySelectedTable(filteredTables, table);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ pub(crate) mod read_queries;
|
||||
pub(crate) mod read_record;
|
||||
pub(crate) mod subscribe;
|
||||
pub(crate) mod test_utils;
|
||||
pub(crate) mod util;
|
||||
pub(crate) mod write_queries;
|
||||
|
||||
mod error;
|
||||
|
||||
@@ -10,6 +10,7 @@ use trailbase_sqlite::{NamedParams, Value};
|
||||
use trailbase_sqlvalue::SqlValue;
|
||||
|
||||
use crate::records::RecordApi;
|
||||
use crate::records::util::named_placeholder;
|
||||
use crate::schema_metadata::{self, JsonColumnMetadata, TableMetadata};
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
@@ -176,7 +177,7 @@ impl Params {
|
||||
files.extend(json_files);
|
||||
}
|
||||
|
||||
named_params.push((prefix_colon(&key).into(), param));
|
||||
named_params.push((named_placeholder(&key).into(), param));
|
||||
column_names.push(key);
|
||||
column_indexes.push(*index);
|
||||
}
|
||||
@@ -223,7 +224,7 @@ impl Params {
|
||||
continue;
|
||||
};
|
||||
|
||||
named_params.push((prefix_colon(&key).into(), value.try_into()?));
|
||||
named_params.push((named_placeholder(&key).into(), value.try_into()?));
|
||||
column_names.push(key);
|
||||
column_indexes.push(*index);
|
||||
}
|
||||
@@ -284,7 +285,7 @@ impl Params {
|
||||
));
|
||||
}
|
||||
|
||||
named_params.push((prefix_colon(&key).into(), param));
|
||||
named_params.push((named_placeholder(&key).into(), param));
|
||||
column_names.push(key);
|
||||
column_indexes.push(*index);
|
||||
}
|
||||
@@ -348,7 +349,7 @@ impl Params {
|
||||
));
|
||||
}
|
||||
|
||||
named_params.push((prefix_colon(&key).into(), param));
|
||||
named_params.push((named_placeholder(&key).into(), param));
|
||||
column_names.push(key);
|
||||
column_indexes.push(*index);
|
||||
}
|
||||
@@ -492,7 +493,7 @@ fn extract_files_from_multipart<S: ColumnAccessor>(
|
||||
}
|
||||
|
||||
named_params.push((
|
||||
prefix_colon(&column.name).into(),
|
||||
named_placeholder(&column.name).into(),
|
||||
Value::Text(serde_json::to_string(&file_metadata)?),
|
||||
));
|
||||
column_names.push(column.name.to_string());
|
||||
@@ -500,7 +501,7 @@ fn extract_files_from_multipart<S: ColumnAccessor>(
|
||||
}
|
||||
"std.FileUploads" => {
|
||||
named_params.push((
|
||||
prefix_colon(&column.name).into(),
|
||||
named_placeholder(&column.name).into(),
|
||||
Value::Text(serde_json::to_string(&file_metadata)?),
|
||||
));
|
||||
column_names.push(column.name.to_string());
|
||||
@@ -612,14 +613,6 @@ fn extract_params_and_files_from_json(
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn prefix_colon(s: &str) -> String {
|
||||
let mut new = String::with_capacity(s.len() + 1);
|
||||
new.push(':');
|
||||
new.push_str(s);
|
||||
return new;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base64::prelude::*;
|
||||
|
||||
@@ -425,15 +425,19 @@ mod test {
|
||||
|
||||
async fn create_test_record_api(state: &AppState, api_name: &str) {
|
||||
let conn = state.conn();
|
||||
|
||||
let table_name = "table 😍";
|
||||
conn
|
||||
.execute(
|
||||
format!(
|
||||
r#"CREATE TABLE 'table' (
|
||||
r#"CREATE TABLE '{table_name}' (
|
||||
id BLOB PRIMARY KEY NOT NULL CHECK(is_uuid_v7(id)) DEFAULT(uuid_v7()),
|
||||
file TEXT CHECK(jsonschema('std.FileUpload', file)),
|
||||
files TEXT CHECK(jsonschema('std.FileUploads', files)),
|
||||
-- Add a "keyword" column to ensure escaping is correct
|
||||
[index] TEXT NOT NULL DEFAULT('')
|
||||
-- Add a "keyword" column to ensure escaping is correct.
|
||||
[index] TEXT NOT NULL DEFAULT(''),
|
||||
-- A special char column to check more escaping.
|
||||
[test 😍] TEXT NOT NULL DEFAULT('')
|
||||
) STRICT"#
|
||||
),
|
||||
(),
|
||||
@@ -447,7 +451,7 @@ mod test {
|
||||
&state,
|
||||
RecordApiConfig {
|
||||
name: Some(api_name.to_string()),
|
||||
table_name: Some("table".to_string()),
|
||||
table_name: Some(table_name.to_string()),
|
||||
acl_world: [
|
||||
PermissionFlag::Create as i32,
|
||||
PermissionFlag::Read as i32,
|
||||
@@ -462,7 +466,6 @@ mod test {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// NOTE: would ideally be in a create_record test instead.
|
||||
#[tokio::test]
|
||||
async fn test_empty_create_record() {
|
||||
let state = test_state(None).await.unwrap();
|
||||
@@ -509,13 +512,10 @@ mod test {
|
||||
Path(API_NAME.to_string()),
|
||||
Query(CreateRecordQuery::default()),
|
||||
None,
|
||||
Either::Json(
|
||||
json_row_from_value(json!({
|
||||
"index": column_value.to_string(),
|
||||
}))
|
||||
.unwrap()
|
||||
.into(),
|
||||
),
|
||||
Either::Json(json!({
|
||||
"index": column_value.to_string(),
|
||||
"test 😍": column_value.to_string(),
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
@@ -542,6 +542,11 @@ mod test {
|
||||
*map.get("index").unwrap(),
|
||||
serde_json::Value::String(column_value.to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
*map.get("test 😍").unwrap(),
|
||||
serde_json::Value::String(column_value.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -14,7 +14,8 @@ use trailbase_sqlite::{Connection, NamedParams, Params as _, Value};
|
||||
use crate::auth::user::User;
|
||||
use crate::config::proto::{ConflictResolutionStrategy, RecordApiConfig};
|
||||
use crate::constants::USER_TABLE;
|
||||
use crate::records::params::{LazyParams, Params, prefix_colon};
|
||||
use crate::records::params::{LazyParams, Params};
|
||||
use crate::records::util::named_placeholder;
|
||||
use crate::records::{Permission, RecordError};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -70,7 +71,7 @@ impl RecordApiSchema {
|
||||
.iter()
|
||||
.map(|meta| {
|
||||
(
|
||||
Cow::Owned(prefix_colon(&meta.column.name)),
|
||||
Cow::Owned(named_placeholder(&meta.column.name)),
|
||||
trailbase_sqlite::Value::Null,
|
||||
)
|
||||
})
|
||||
@@ -729,7 +730,7 @@ struct SubscriptionAclParams<'a> {
|
||||
impl<'a> trailbase_sqlite::Params for SubscriptionAclParams<'a> {
|
||||
fn bind(self, stmt: &mut rusqlite::Statement<'_>) -> rusqlite::Result<()> {
|
||||
for (name, v) in self.params {
|
||||
if let Some(idx) = stmt.parameter_index(&prefix_colon(name))? {
|
||||
if let Some(idx) = stmt.parameter_index(&named_placeholder(name))? {
|
||||
stmt.raw_bind_parameter(idx, v)?;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#[inline]
|
||||
pub(crate) fn named_placeholder(s: &str) -> String {
|
||||
let mut new = String::with_capacity(s.len() + 1);
|
||||
new.push(':');
|
||||
for char in s.chars() {
|
||||
new.push(if char.is_alphanumeric() { char } else { '_' });
|
||||
}
|
||||
return new;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ FROM
|
||||
{% if !column_names.is_empty() -%}
|
||||
, (SELECT
|
||||
{%- for name in column_names -%}
|
||||
{% if !loop.first %},{% endif %} :{{ name }} AS "{{ name }}"
|
||||
{% if !loop.first %},{% endif %} {{ crate::records::util::named_placeholder(name) }} AS "{{ name }}"
|
||||
{%- endfor -%}
|
||||
) AS _REQ_
|
||||
{%- endif %}
|
||||
|
||||
@@ -6,7 +6,7 @@ INSERT {{ conflict_clause }} INTO {{ table_name }}
|
||||
{%- endfor -%}
|
||||
) VALUES (
|
||||
{%- for name in column_names -%}
|
||||
{%- if !loop.first %},{% endif %}:{{ name }}
|
||||
{%- if !loop.first %},{% endif %}{{ crate::records::util::named_placeholder(name) }}
|
||||
{%- endfor -%}
|
||||
)
|
||||
{%- endif -%}
|
||||
|
||||
@@ -5,7 +5,7 @@ FROM
|
||||
{% if !column_names.is_empty() -%}
|
||||
, (SELECT
|
||||
{%- for name in column_names -%}
|
||||
{% if !loop.first %},{% endif %} :{{ name }} AS "{{ name }}"
|
||||
{% if !loop.first %},{% endif %} {{ crate::records::util::named_placeholder(name) }} AS "{{ name }}"
|
||||
{%- endfor -%}
|
||||
) AS _ROW_
|
||||
{%- endif %}
|
||||
|
||||
@@ -7,7 +7,7 @@ FROM
|
||||
{% if !column_names.is_empty() -%}
|
||||
, (SELECT
|
||||
{%- for name in column_names -%}
|
||||
{% if !loop.first %},{% endif %} :{{ name }} AS "{{ name }}"
|
||||
{% if !loop.first %},{% endif %} {{ crate::records::util::named_placeholder(name) }} AS "{{ name }}"
|
||||
{%- endfor -%}
|
||||
) AS _REQ_
|
||||
{%- endif %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
UPDATE {{ table_name }} SET
|
||||
{%- for name in column_names -%}
|
||||
{%- if !loop.first %},{% endif %} "{{ name }}" = :{{ name }}
|
||||
{%- if !loop.first %},{% endif %} "{{ name }}" = {{ crate::records::util::named_placeholder(name) }}
|
||||
{%- endfor %}
|
||||
WHERE "{{ pk_column_name }}" = :__pk_value
|
||||
{%- match returning -%}
|
||||
|
||||
@@ -551,10 +551,14 @@ impl QualifiedName {
|
||||
}
|
||||
|
||||
pub fn migration_filename(&self, prefix: &str) -> String {
|
||||
fn sanitize(s: &str) -> String {
|
||||
return s.replace(|c: char| !c.is_alphanumeric(), "_");
|
||||
}
|
||||
|
||||
return if let Some(ref db) = self.database_schema {
|
||||
format!("{prefix}_{db}_{}", self.name)
|
||||
format!("{prefix}_{db}_{}", sanitize(&self.name), db = sanitize(db))
|
||||
} else {
|
||||
format!("{prefix}_{}", self.name)
|
||||
format!("{prefix}_{}", sanitize(&self.name))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user