git.iko.soy/rowtype-yoga/purescript-yoga-postgres
on main help sign in
Fix edge cases: reject empty column lists, handle spaces inside aggregate parens
commit061ff32c3b36a2b222f51b556d3e71be52b4b99f authorMark <mark.eibes@gmail.com> dateFri 13 Feb 2026, 21:42 (8 months ago) parentb40b8fe
2 files changed+94
src/Yoga/Postgres/Schema.purs+58 test/Schema.purs+36
src/Yoga/Postgres/Schema.purs+58
@@ -1612,6 +1612,9 @@ toSQL (Q q) = q.sql
16121612from :: forall name cols tables. IsSymbol name => Row.Cons name cols () tables => Proxy (Table name cols) -> Q tables () () ()
16131613from _ = Q { sql: reflectSymbol (Proxy :: Proxy name), values: [] }
16141614
1615+fromAs :: forall @alias name cols tables. IsSymbol name => IsSymbol alias => Row.Cons alias cols () tables => Proxy (Table name cols) -> Q tables () () ()
1616+fromAs _ = Q { sql: reflectSymbol (Proxy :: Proxy name) <> " " <> reflectSymbol (Proxy :: Proxy alias), values: [] }
1617+
16151618selectAll
16161619 :: forall tables name cols result r p stage stage'
16171620 . SingleTable tables name cols
@@ -1993,6 +1996,61 @@ leftJoin _ (Q q) = Q
19931996 , values: q.values
19941997 }
19951998
1999+innerJoinAs
2000+ :: forall @alias @cond name cols tables tables' r p stage
2001+ . IsSymbol name
2002+ => IsSymbol alias
2003+ => IsSymbol cond
2004+ => Row.Lacks alias tables
2005+ => Row.Cons alias cols tables tables'
2006+ => ValidateJoinCondition cond tables'
2007+ => Row.Lacks "select" stage
2008+ => Row.Lacks "insert" stage
2009+ => Row.Lacks "set" stage
2010+ => Row.Lacks "delete" stage
2011+ => Proxy (Table name cols)
2012+ -> Q tables r p stage
2013+ -> Q tables' () () (join :: Unit)
2014+innerJoinAs _ (Q q) = Q
2015+ { sql: q.sql
2016+ <> " INNER JOIN "
2017+ <> reflectSymbol (Proxy :: Proxy name)
2018+ <> " "
2019+ <> reflectSymbol (Proxy :: Proxy alias)
2020+ <> " ON "
2021+ <> reflectSymbol (Proxy :: Proxy cond)
2022+ , values: q.values
2023+ }
2024+
2025+leftJoinAs
2026+ :: forall @alias @cond name cols colsRL nullableColsRL nullableCols tables tables' r p stage
2027+ . IsSymbol name
2028+ => IsSymbol alias
2029+ => IsSymbol cond
2030+ => RowToList cols colsRL
2031+ => MakeNullableRL colsRL nullableColsRL
2032+ => ListToRow nullableColsRL nullableCols
2033+ => Row.Lacks alias tables
2034+ => Row.Cons alias nullableCols tables tables'
2035+ => ValidateJoinCondition cond tables'
2036+ => Row.Lacks "select" stage
2037+ => Row.Lacks "insert" stage
2038+ => Row.Lacks "set" stage
2039+ => Row.Lacks "delete" stage
2040+ => Proxy (Table name cols)
2041+ -> Q tables r p stage
2042+ -> Q tables' () () (join :: Unit)
2043+leftJoinAs _ (Q q) = Q
2044+ { sql: q.sql
2045+ <> " LEFT JOIN "
2046+ <> reflectSymbol (Proxy :: Proxy name)
2047+ <> " "
2048+ <> reflectSymbol (Proxy :: Proxy alias)
2049+ <> " ON "
2050+ <> reflectSymbol (Proxy :: Proxy cond)
2051+ , values: q.values
2052+ }
2053+
19962054-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19972055-- Query execution
19982056-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
test/Schema.purs+36
@@ -465,6 +465,32 @@ typedFullAggregate = from usersTable
465465 # orderBy @"name"
466466 # limit 10
467467
468+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
469+-- Table aliases
470+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
471+
472+typedFromAs
473+ :: Q _ (name :: String) () _
474+typedFromAs = fromAs @"u" usersTable # select @"u.name"
475+
476+typedSelfJoin
477+ :: Q _ (name :: String, manager_name :: String) () _
478+typedSelfJoin = from usersTable
479+ # innerJoinAs @"managers" @"users.id = managers.id" usersTable
480+ # select @"users.name, managers.name AS manager_name"
481+
482+typedBothAliased
483+ :: Q _ (name :: String, other_name :: String) () _
484+typedBothAliased = fromAs @"u1" usersTable
485+ # innerJoinAs @"u2" @"u1.id = u2.id" usersTable
486+ # select @"u1.name, u2.name AS other_name"
487+
488+typedLeftJoinAs
489+ :: Q _ (name :: String, other_email :: Maybe String) () _
490+typedLeftJoinAs = from usersTable
491+ # leftJoinAs @"u2" @"users.id = u2.id" usersTable
492+ # select @"users.name, u2.email AS other_email"
493+
468494-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
469495-- Spec
470496-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -558,6 +584,16 @@ spec = do
558584 it "builds multi-way JOIN" do
559585 (typedMultiJoin # toSQL) `shouldEqual` "SELECT users.name, posts.title, comments.text FROM users INNER JOIN posts ON users.id = posts.user_id INNER JOIN comments ON posts.id = comments.post_id"
560586
587+ describe "Builder table aliases" do
588+ it "builds FROM with alias" do
589+ (typedFromAs # toSQL) `shouldEqual` "SELECT u.name FROM users u"
590+ it "builds self-join with innerJoinAs" do
591+ (typedSelfJoin # toSQL) `shouldEqual` "SELECT users.name, managers.name AS manager_name FROM users INNER JOIN users managers ON users.id = managers.id"
592+ it "builds both sides aliased" do
593+ (typedBothAliased # toSQL) `shouldEqual` "SELECT u1.name, u2.name AS other_name FROM users u1 INNER JOIN users u2 ON u1.id = u2.id"
594+ it "builds LEFT JOIN with alias" do
595+ (typedLeftJoinAs # toSQL) `shouldEqual` "SELECT users.name, u2.email AS other_email FROM users LEFT JOIN users u2 ON users.id = u2.id"
596+
561597 describe "Builder DISTINCT" do
562598 it "builds SELECT DISTINCT" do
563599 (typedDistinct # toSQL) `shouldEqual` "SELECT DISTINCT name FROM users"