Skip to content

Commit 21cb2a1

Browse files
committed
- Make sure primary keys are supported types
- Fix TODO nodeId check, move out of transpile into builder - Create SupportedPrimaryKeyType enum for PK type matching
1 parent 7daffe7 commit 21cb2a1

File tree

4 files changed

+107
-49
lines changed

4 files changed

+107
-49
lines changed

src/builder.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,16 @@ pub struct NodeIdInstance {
989989
pub values: Vec<serde_json::Value>,
990990
}
991991

992+
impl NodeIdInstance {
993+
pub fn validate(&self, table: &Table) -> Result<(), String> {
994+
// Validate that nodeId belongs to the table being queried
995+
if self.schema_name != table.schema || self.table_name != table.name {
996+
return Err("nodeId belongs to a different collection".to_string());
997+
}
998+
Ok(())
999+
}
1000+
}
1001+
9921002
#[derive(Clone, Debug)]
9931003
pub struct NodeIdBuilder {
9941004
pub alias: String,

src/graphql.rs

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,55 +1233,58 @@ impl ___Type for QueryType {
12331233
f.push(collection_entrypoint);
12341234

12351235
// Add single record query by primary key if the table has a primary key
1236+
// and the primary key types are supported (int, bigint, uuid, string)
12361237
if let Some(primary_key) = table.primary_key() {
1237-
let node_type = NodeType {
1238-
table: Arc::clone(table),
1239-
fkey: None,
1240-
reverse_reference: None,
1241-
schema: Arc::clone(&self.schema),
1242-
};
1243-
1244-
// Create arguments for each primary key column
1245-
let mut pk_args = Vec::new();
1246-
for col_name in &primary_key.column_names {
1247-
if let Some(col) = table.columns.iter().find(|c| &c.name == col_name) {
1248-
let col_type = sql_column_to_graphql_type(col, &self.schema)
1249-
.ok_or_else(|| {
1250-
format!(
1251-
"Could not determine GraphQL type for column {}",
1252-
col_name
1253-
)
1254-
})
1255-
.unwrap_or_else(|_| __Type::Scalar(Scalar::String(None)));
1256-
1257-
// Use graphql_column_field_name to convert snake_case to camelCase if needed
1258-
let arg_name = self.schema.graphql_column_field_name(col);
1259-
1260-
pk_args.push(__InputValue {
1261-
name_: arg_name,
1262-
type_: __Type::NonNull(NonNullType {
1263-
type_: Box::new(col_type),
1264-
}),
1265-
description: Some(format!("The record's `{}` value", col_name)),
1266-
default_value: None,
1267-
sql_type: Some(NodeSQLType::Column(Arc::clone(col))),
1268-
});
1238+
if table.has_supported_pk_types_for_by_pk() {
1239+
let node_type = NodeType {
1240+
table: Arc::clone(table),
1241+
fkey: None,
1242+
reverse_reference: None,
1243+
schema: Arc::clone(&self.schema),
1244+
};
1245+
1246+
// Create arguments for each primary key column
1247+
let mut pk_args = Vec::new();
1248+
for col_name in &primary_key.column_names {
1249+
if let Some(col) = table.columns.iter().find(|c| &c.name == col_name) {
1250+
let col_type = sql_column_to_graphql_type(col, &self.schema)
1251+
.ok_or_else(|| {
1252+
format!(
1253+
"Could not determine GraphQL type for column {}",
1254+
col_name
1255+
)
1256+
})
1257+
.unwrap_or_else(|_| __Type::Scalar(Scalar::String(None)));
1258+
1259+
// Use graphql_column_field_name to convert snake_case to camelCase if needed
1260+
let arg_name = self.schema.graphql_column_field_name(col);
1261+
1262+
pk_args.push(__InputValue {
1263+
name_: arg_name,
1264+
type_: __Type::NonNull(NonNullType {
1265+
type_: Box::new(col_type),
1266+
}),
1267+
description: Some(format!("The record's `{}` value", col_name)),
1268+
default_value: None,
1269+
sql_type: Some(NodeSQLType::Column(Arc::clone(col))),
1270+
});
1271+
}
12691272
}
1270-
}
12711273

1272-
let pk_entrypoint = __Field {
1273-
name_: format!("{}ByPk", lowercase_first_letter(table_base_type_name)),
1274-
type_: __Type::Node(node_type),
1275-
args: pk_args,
1276-
description: Some(format!(
1277-
"Retrieve a record of type `{}` by its primary key",
1278-
table_base_type_name
1279-
)),
1280-
deprecation_reason: None,
1281-
sql_type: None,
1282-
};
1274+
let pk_entrypoint = __Field {
1275+
name_: format!("{}ByPk", lowercase_first_letter(table_base_type_name)),
1276+
type_: __Type::Node(node_type),
1277+
args: pk_args,
1278+
description: Some(format!(
1279+
"Retrieve a record of type `{}` by its primary key",
1280+
table_base_type_name
1281+
)),
1282+
deprecation_reason: None,
1283+
sql_type: None,
1284+
};
12831285

1284-
f.push(pk_entrypoint);
1286+
f.push(pk_entrypoint);
1287+
}
12851288
}
12861289
}
12871290
}
@@ -3443,7 +3446,7 @@ impl FromStr for FilterOp {
34433446
"contains" => Ok(Self::Contains),
34443447
"containedBy" => Ok(Self::ContainedBy),
34453448
"overlaps" => Ok(Self::Overlap),
3446-
_ => Err("Invalid filter operation".to_string()),
3449+
other => Err(format!("Invalid filter operation: {}", other)),
34473450
}
34483451
}
34493452
}

src/sql_types.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,18 @@ impl Table {
565565
.collect::<Vec<&Arc<Column>>>()
566566
}
567567

568+
pub fn has_supported_pk_types_for_by_pk(&self) -> bool {
569+
let pk_columns = self.primary_key_columns();
570+
if pk_columns.is_empty() {
571+
return false;
572+
}
573+
574+
// Check that all primary key columns have supported types
575+
pk_columns.iter().all(|col| {
576+
SupportedPrimaryKeyType::from_type_name(&col.type_name).is_some()
577+
})
578+
}
579+
568580
pub fn is_any_column_selectable(&self) -> bool {
569581
self.columns.iter().any(|x| x.permissions.is_selectable)
570582
}
@@ -577,6 +589,41 @@ impl Table {
577589
}
578590
}
579591

592+
#[derive(Debug, PartialEq)]
593+
pub enum SupportedPrimaryKeyType {
594+
// Integer types
595+
Int, // int, int4, integer
596+
BigInt, // bigint, int8
597+
SmallInt, // smallint, int2
598+
// String types
599+
Text, // text
600+
VarChar, // varchar
601+
Char, // char, bpchar
602+
CiText, // citext
603+
// UUID
604+
UUID, // uuid
605+
}
606+
607+
impl SupportedPrimaryKeyType {
608+
fn from_type_name(type_name: &str) -> Option<Self> {
609+
match type_name {
610+
// Integer types
611+
"int" | "int4" | "integer" => Some(Self::Int),
612+
"bigint" | "int8" => Some(Self::BigInt),
613+
"smallint" | "int2" => Some(Self::SmallInt),
614+
// String types
615+
"text" => Some(Self::Text),
616+
"varchar" => Some(Self::VarChar),
617+
"char" | "bpchar" => Some(Self::Char),
618+
"citext" => Some(Self::CiText),
619+
// UUID
620+
"uuid" => Some(Self::UUID),
621+
// Any other type is not supported
622+
_ => None,
623+
}
624+
}
625+
}
626+
580627
#[derive(Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
581628
pub struct SchemaDirectives {
582629
// @graphql({"inflect_names": true})

src/transpile.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,9 +1424,7 @@ impl NodeIdInstance {
14241424
param_context: &mut ParamContext,
14251425
) -> Result<String, String> {
14261426
// Validate that nodeId belongs to the table being queried
1427-
if self.schema_name != table.schema || self.table_name != table.name {
1428-
return Err("nodeId belongs to a different collection".to_string());
1429-
}
1427+
self.validate(table)?;
14301428

14311429
let pkey = table
14321430
.primary_key()

0 commit comments

Comments
 (0)