Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions src/ts_generator/sql_parser/quoted_strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,20 @@ pub struct DisplayObjectName<'a>(pub &'a ObjectName);
impl fmt::Display for DisplayObjectName<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// In sqlparser 0.59.0, ObjectName contains Vec<ObjectNamePart> instead of Vec<Ident>
// We need to extract the Ident from the first ObjectNamePart
let first_part = &self.0 .0[0];
if let Some(ident) = first_part.as_ident() {
// For qualified table names (e.g., database.schema.table), we want the last identifier (table name)
let last_part = self.0 .0.last().expect(
"ObjectName must contain at least one part (sqlparser invariant).\
If you're seeing this, it's a bug in sqlparser or the SQL parsing logic.",
);
Comment thread
JasonShin marked this conversation as resolved.

if let Some(ident) = last_part.as_ident() {
Comment thread
JasonShin marked this conversation as resolved.
let quote_style = &ident.quote_style;
let name = &ident.value;
let name = trim_table_name(name, quote_style);
write!(f, "{name}")
} else {
// Fallback: if it's a function-based name, use the default Display implementation
write!(f, "{}", first_part)
write!(f, "{}", last_part)
}
}
}
Expand All @@ -53,3 +57,68 @@ impl fmt::Display for DisplayTableAlias<'_> {
write!(f, "{name}")
}
}

#[cfg(test)]
mod tests {
use super::*;
use sqlparser::ast::{SetExpr, Statement, TableFactor};
use sqlparser::dialect::PostgreSqlDialect;
use sqlparser::parser::Parser;

fn parse_table_name(sql: &str) -> ObjectName {
let dialect = PostgreSqlDialect {};
let statements = Parser::parse_sql(&dialect, sql).expect("Failed to parse SQL");
let statement = &statements[0];

if let Statement::Query(query) = statement {
if let SetExpr::Select(select) = query.body.as_ref() {
if let Some(table_with_joins) = select.from.first() {
if let TableFactor::Table { name, .. } = &table_with_joins.relation {
return name.clone();
}
}
}
}
panic!("Could not extract table name from SQL");
}

#[test]
fn test_display_object_name_single_identifier() {
// Test simple table name: "users"
let object_name = parse_table_name("SELECT * FROM users");
let display = DisplayObjectName(&object_name);
assert_eq!(display.to_string(), "users");
}

#[test]
fn test_display_object_name_qualified_schema_table() {
// Test qualified name: "public.users" -> should return "users"
let object_name = parse_table_name("SELECT * FROM public.users");
let display = DisplayObjectName(&object_name);
assert_eq!(display.to_string(), "users");
}

#[test]
fn test_display_object_name_qualified_database_schema_table() {
// Test fully qualified name: "database.schema.table" -> should return "table"
Comment thread
JasonShin marked this conversation as resolved.
Comment thread
JasonShin marked this conversation as resolved.
let object_name = parse_table_name("SELECT * FROM mydb.public.users");
let display = DisplayObjectName(&object_name);
assert_eq!(display.to_string(), "users");
Comment thread
JasonShin marked this conversation as resolved.
}

#[test]
fn test_display_object_name_with_quotes() {
// Test quoted identifier: `"my_table"` -> should return "my_table" without quotes
let object_name = parse_table_name("SELECT * FROM \"my_table\"");
let display = DisplayObjectName(&object_name);
assert_eq!(display.to_string(), "my_table");
}

#[test]
fn test_display_object_name_qualified_with_quotes() {
// Test qualified with quotes: `"public"."my_table"` -> should return "my_table"
let object_name = parse_table_name("SELECT * FROM \"public\".\"my_table\"");
let display = DisplayObjectName(&object_name);
assert_eq!(display.to_string(), "my_table");
}
}
39 changes: 39 additions & 0 deletions tests/demo/select/qualified_table_names.queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export type QualifiedTableNameParams = [];

export interface IQualifiedTableNameResult {
racesId: number;
racesName: 'human' | 'night elf' | 'dwarf' | 'gnome' | 'orc' | 'troll' | 'tauren' | 'undead';
}

export interface IQualifiedTableNameQuery {
params: QualifiedTableNameParams;
result: IQualifiedTableNameResult;
}

export type QualifiedTableNameJoinParams = [];

export interface IQualifiedTableNameJoinResult {
factionId: number;
factionName: 'alliance' | 'horde';
raceId: number;
raceName: 'human' | 'night elf' | 'dwarf' | 'gnome' | 'orc' | 'troll' | 'tauren' | 'undead';
}

export interface IQualifiedTableNameJoinQuery {
params: QualifiedTableNameJoinParams;
result: IQualifiedTableNameJoinResult;
}

export type MixedQualifiedNamesParams = [];

export interface IMixedQualifiedNamesResult {
factionId: number;
factionName: 'alliance' | 'horde';
raceId: number;
raceName: 'human' | 'night elf' | 'dwarf' | 'gnome' | 'orc' | 'troll' | 'tauren' | 'undead';
}

export interface IMixedQualifiedNamesQuery {
params: MixedQualifiedNamesParams;
result: IMixedQualifiedNamesResult;
}
40 changes: 40 additions & 0 deletions tests/demo/select/qualified_table_names.snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export type QualifiedTableNameParams = [];

export interface IQualifiedTableNameResult {
racesId: number;
racesName: 'human' | 'night elf' | 'dwarf' | 'gnome' | 'orc' | 'troll' | 'tauren' | 'undead';
}

export interface IQualifiedTableNameQuery {
params: QualifiedTableNameParams;
result: IQualifiedTableNameResult;
}

export type QualifiedTableNameJoinParams = [];

export interface IQualifiedTableNameJoinResult {
factionId: number;
factionName: 'alliance' | 'horde';
raceId: number;
raceName: 'human' | 'night elf' | 'dwarf' | 'gnome' | 'orc' | 'troll' | 'tauren' | 'undead';
}

export interface IQualifiedTableNameJoinQuery {
params: QualifiedTableNameJoinParams;
result: IQualifiedTableNameJoinResult;
}

export type MixedQualifiedNamesParams = [];

export interface IMixedQualifiedNamesResult {
factionId: number;
factionName: 'alliance' | 'horde';
raceId: number;
raceName: 'human' | 'night elf' | 'dwarf' | 'gnome' | 'orc' | 'troll' | 'tauren' | 'undead';
}

export interface IMixedQualifiedNamesQuery {
params: MixedQualifiedNamesParams;
result: IMixedQualifiedNamesResult;
}

37 changes: 37 additions & 0 deletions tests/demo/select/qualified_table_names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { sql } from 'sqlx-ts'

// Test qualified table name (schema.table)
const qualifiedTableName = sql`
-- @name: qualified table name
SELECT
races.id,
races.name
FROM public.races
WHERE races.id = 1
`

// Test multiple qualified table names in a JOIN
const qualifiedTableNameJoin = sql`
-- @name: qualified table name join
SELECT
races.id AS race_id,
races.name AS race_name,
factions.id AS faction_id,
factions.name AS faction_name
FROM public.races
INNER JOIN public.factions ON races.faction_id = factions.id
WHERE races.id = 1
`

// Test mixed qualified and unqualified table names
const mixedQualifiedNames = sql`
-- @name: mixed qualified names
SELECT
r.id AS race_id,
r.name AS race_name,
f.id AS faction_id,
f.name AS faction_name
FROM public.races AS r
INNER JOIN factions AS f ON r.faction_id = f.id
WHERE r.id = 1
`
Loading