diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -88,3 +88,125 @@
 * `WHERE CURRENT OF` for cursors
 * Complex relation expressions in `UPDATE` or `DELETE` targets
 * Column indirection in `UPDATE SET` clauses
+
+## Supported features.
+
+This is the output from the test suite, which gives a pretty good
+indication of the supported SQL language features.
+
+```
+queries
+  select * from users [✔]
+  select * from public.users [✔]
+  SELECT * FROM "users" AS "users" [✔]
+  select * from users where name = 'bob' [✔]
+  select users.name from users [✔]
+  select name from users [✔]
+  select count(*) from users group by () [✔]
+  select name, id from users [✔]
+  select id, name from users [✔]
+  select users.id, employee_id from users [✔]
+  select users.* from users [✔]
+  select users.* from other.users [✔]
+  select * from users limit 3 [✔]
+  select * from users limit inline(lim) [✔]
+  select * from users offset inline(off) [✔]
+  select * from users offset 1 [✔]
+  select users.id, employee_id as emp_id from users [✔]
+  select users.id as user_id, employee_id from users [✔]
+  select users.id from users left outer join emails on emails.user_id = users.id [✔]
+  select users.id, users.name, emails.email from users left outer join emails on emails.user_id = users.id where emails.email = inline("targetEmail") [✔]
+  select 'text_val' [✔]
+  select 1 [✔]
+  select 1 AS num, 'text_val' AS txt [✔]
+  group by
+    select name from users group by name [✔]
+    select employee_id, count(id) from users group by employee_id [✔]
+    select employee_id, name, count(id) from users group by employee_id, name [✔]
+  common table expressions
+    with users_cte as (select * from users) select * from users_cte [✔]
+    with users_cte as (select * from users), emails_cte as (select * from emails) select users_cte.*, emails_cte.email from users_cte join emails_cte on users_cte.id = emails_cte.user_id [✔]
+inserts
+  insert into emails (id, user_id, email) values (1, 'user-1', 'foo@bar') [✔]
+  insert into emails (id, user_id, email) values (1, 'user-1', $1) [✔]
+  insert into emails (id, user_id, email) values (1, $2, $1) [✔]
+  insert into emails (id, user_id, email) values (inline(i), inline(uid), inline_param(e)) [✔]
+  default keyword
+    insert into emails (id, user_id, email) values (default, 'foo', 'bar') [✔]
+    insert into emails (id, user_id, email) values (deFault, 'foo', 'bar') [✔]
+    insert into emails (id, user_id, email) values (DEFAULT, 'foo', 'bar') [✔]
+  null keyword
+    insert into emails (id, user_id, email) values (DEFAULT, 'foo', null) [✔]
+    insert into emails (id, user_id, email) values (DEFAULT, 'foo', NULL) [✔]
+    insert into emails (id, user_id, email) values (DEFAULT, 'foo', NuLL) [✔]
+  insert ... select ...
+    insert into emails select id, user_id, email from emails where id = 1 [✔]
+    insert into emails select id, user_id, email from emails where id = $1 [✔]
+    insert into users_copy select id, name, bio from users where users.id = 'uid1' [✔]
+  returning clause
+    insert into emails (id, user_id, email) values (1, 'user-1', 'foo@bar') returning id [✔]
+    insert into emails (id, user_id, email) values (1, 'user-1', 'foo@bar') returning * [✔]
+  with common table expressions
+    with new_user (id, name, bio) as (values ('id_new', 'new_name', 'new_bio')) insert into users_copy select * from new_user [✔]
+deletes
+  delete from users where true [✔]
+  delete from emails where id = 1 [✔]
+  delete from emails where email = inline(e) [✔]
+  delete from users where id = 'some-id' returning id [✔]
+  with common table expressions
+    with to_delete as (select id from users where name = 'Alice') delete from users where id in (select to_delete.id from to_delete) [✔]
+    with to_delete as (select id from users where name = 'Alice') delete from users using to_delete where users.id = to_delete.id [✔]
+updates
+  update users set name = 'new name' where id = 'some-id' [✔]
+  update users set name = 'new name', bio = 'new bio' where id = 'some-id' [✔]
+  update users set name = inline(n) where id = 'some-id' [✔]
+  update users set name = 'new name' where id = 'some-id' returning id [✔]
+  with common table expressions
+    with to_update as (select id from users where name = 'Alice') update users set name = 'Alicia' from to_update where users.id = to_update.id [✔]
+scalar expressions
+  select users.id != 'no-such-user' as neq from users [✔]
+  select * from users where users.id <> 'no-such-user' [✔]
+  select * from emails where emails.id > 0 [✔]
+  select * from emails where emails.id >= 0 [✔]
+  select * from emails where emails.id < 10 [✔]
+  select * from emails where emails.id <= 10 [✔]
+  select emails.id + 1 as plus_one from emails [✔]
+  select emails.id - 1 as minus_one from emails [✔]
+  select emails.id * 2 as times_two from emails [✔]
+  select * from users where users.id = 'a' and users.name = 'b' [✔]
+  select * from users where users.id = 'a' or users.name = 'b' [✔]
+  select * from users where users.name like 'A%' [✔]
+  select * from users where users.name ilike 'a%' [✔]
+  select * from users where not (users.name = 'no-one') [✔]
+  select -emails.id as neg_id from emails [✔]
+  select * from users where users.bio is null [✔]
+  select * from users where users.bio is not null [✔]
+  function calls
+    select coalesce(users.bio, 'no bio') as bio from users [✔]
+    select lower(users.name) as lower_name from users [✔]
+    select char_length(users.name) as name_len from users [✔]
+    select character_length(users.name) as name_len_alias from users [✔]
+    select "upper"(users.name) as upper_name from users [✔]
+    select now() as current_time [✔]
+    select current_date as today [✔]
+    haskell variables in expressions [✔]
+  select (emails.id + 1) * 2 as calc from emails [✔]
+  select * from users where users.name in ('Alice', 'Bob') [✔]
+  select * from users where users.name not in ('Alice', 'Bob') [✔]
+  select * from emails where emails.id between 0 and 10 [✔]
+  select * from emails where emails.id not between 0 and 10 [✔]
+  select (e.id :: text) as casted_id from emails as e [✔]
+  select * from users for update [✔]
+  select * from jsonb_test [✔]
+  select * from json_test [✔]
+  select distinct name from users [✔]
+  select distinct * from users [✔]
+  select distinct on (employee_id) employee_id, name from users [✔]
+  select distinct on (employee_id, name) employee_id, name, id from users [✔]
+  order by
+    select * from users order by name [✔]
+    select * from users order by name asc [✔]
+    select * from users order by name desc [✔]
+  having clause
+    select employee_id, count(id) from users group by employee_id having count(id) > 1 [✔]
+```
diff --git a/squeal-postgresql-qq.cabal b/squeal-postgresql-qq.cabal
--- a/squeal-postgresql-qq.cabal
+++ b/squeal-postgresql-qq.cabal
@@ -1,6 +1,6 @@
 cabal-version:       3.0
 name:                squeal-postgresql-qq
-version:             0.1.0.0
+version:             0.1.1.0
 synopsis:            QuasiQuoter transforming raw sql into Squeal expressions.
 -- description:         
 homepage:            https://github.com/owensmurray/squeal-postgresql-qq
@@ -21,7 +21,7 @@
     , base              >= 4.18.3.0 && < 4.22
     , bytestring        >= 0.11.3.0 && < 0.13
     , generics-sop      >= 0.5.1.3  && < 0.6
-    , postgresql-syntax >= 0.4      && < 0.5
+    , postgresql-syntax >= 0.4.1    && < 0.5
     , squeal-postgresql >= 0.9.1.3  && < 0.10
     , template-haskell  >= 2.20.0.0 && < 2.24
     , text              >= 1.2.5.0  && < 2.2
@@ -41,7 +41,6 @@
   exposed-modules:     
     Squeal.QuasiQuotes
   other-modules:
-    Squeal.QuasiQuotes.Common
     Squeal.QuasiQuotes.Delete
     Squeal.QuasiQuotes.Insert
     Squeal.QuasiQuotes.Query
diff --git a/src/Squeal/QuasiQuotes.hs b/src/Squeal/QuasiQuotes.hs
--- a/src/Squeal/QuasiQuotes.hs
+++ b/src/Squeal/QuasiQuotes.hs
@@ -22,8 +22,8 @@
   )
 import Language.Haskell.TH.Syntax (Exp(AppE, VarE), Q, runIO)
 import Prelude
-  ( Applicative(pure), Either(Left, Right), MonadFail(fail), Semigroup((<>))
-  , Show(show), ($), (.), String, error, print
+  ( Applicative(pure), Either(Left, Right), Maybe(Nothing), MonadFail(fail)
+  , Semigroup((<>)), Show(show), ($), (.), String, error, print
   )
 import Squeal.QuasiQuotes.Delete (toSquealDelete)
 import Squeal.QuasiQuotes.Insert (toSquealInsert)
@@ -161,7 +161,7 @@
 toSquealStatement :: PGT_AST.PreparableStmt -> Q Exp
 toSquealStatement = \case
   PGT_AST.SelectPreparableStmt theQuery -> do
-    queryExp <- toSquealQuery theQuery
+    queryExp <- toSquealQuery [] Nothing theQuery
     pure $ VarE 'monoQuery `AppE` queryExp
   PGT_AST.InsertPreparableStmt stmt -> do
     manipExp <- toSquealInsert stmt
diff --git a/src/Squeal/QuasiQuotes/Common.hs b/src/Squeal/QuasiQuotes/Common.hs
deleted file mode 100644
--- a/src/Squeal/QuasiQuotes/Common.hs
+++ /dev/null
@@ -1,986 +0,0 @@
-{-# LANGUAGE GHC2021 #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskellQuotes #-}
-{-# LANGUAGE ViewPatterns #-}
-
--- | Commonplace renderers shared by other modules.
-module Squeal.QuasiQuotes.Common (
-  renderPGTTableRef,
-  renderPGTAExpr,
-  getIdentText,
-  renderPGTTargeting,
-  renderPGTTargetList,
-) where
-
-import Control.Applicative (Alternative((<|>)))
-import Control.Monad (when)
-import Data.Foldable (Foldable(elem, foldl', null))
-import Data.Maybe (isJust)
-import Data.String (IsString(fromString))
-import Language.Haskell.TH.Syntax
-  ( Exp(AppE, AppTypeE, ConE, InfixE, LabelE, ListE, LitE, TupE, VarE)
-  , Lit(IntegerL, StringL), TyLit(NumTyLit), Type(LitT), Name, Q, mkName
-  )
-import Prelude
-  ( Applicative(pure), Bool(False, True), Either(Left, Right), Eq((==))
-  , Functor(fmap), Maybe(Just, Nothing), MonadFail(fail)
-  , Num((*), (+), (-), fromInteger), Ord((<)), Semigroup((<>)), Show(show)
-  , Traversable(mapM), ($), (&&), (.), (<$>), (||), Int, Integer, any, either
-  , error, fromIntegral, id
-  )
-import qualified Data.ByteString.Char8 as BS8
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Text as Text
-import qualified PostgresqlSyntax.Ast as PGT_AST
-import qualified Squeal.PostgreSQL as S
-
-
-getIdentText :: PGT_AST.Ident -> Text.Text
-getIdentText = \case
-  PGT_AST.QuotedIdent t -> t
-  PGT_AST.UnquotedIdent t -> t
-
-
-renderPGTTableRef :: NE.NonEmpty PGT_AST.TableRef -> Q Exp
-renderPGTTableRef tableRefs = do
-  renderedTableRefs <- mapM renderSingleTableRef (NE.toList tableRefs)
-  case renderedTableRefs of
-    [] -> fail "Empty FROM clause" -- Should not happen with NonEmpty
-    (firstTbl : restTbls) ->
-      -- For FROM t1, t2, t3 Squeal uses: (table #t1) & also (table #t2) & also (table #t3)
-      -- S.also takes new item first, then accumulated.
-      -- So foldl' (\acc item -> VarE 'S.also `AppE` item `AppE` acc) firstTbl restTbls
-      -- However, Squeal's FromClause Additional instance is `also right left`, meaning `also new current`.
-      -- So `foldl (\current new -> VarE 'S.also `AppE` new `AppE` current) firstTbl restTbls` is correct.
-      pure $ foldl' (\acc tbl -> VarE 'S.also `AppE` tbl `AppE` acc) firstTbl restTbls
-
-
-renderSingleTableRef :: PGT_AST.TableRef -> Q Exp
-renderSingleTableRef = \case
-  PGT_AST.RelationExprTableRef relationExpr maybeAliasClause sampleClause -> do
-    when (isJust sampleClause) $ fail "TABLESAMPLE clause is not supported yet."
-    renderPGTRelationExprTableRef relationExpr maybeAliasClause
-  PGT_AST.JoinTableRef joinedTable maybeAliasClause ->
-    -- If `maybeAliasClause` is Just, it means `(JOIN_TABLE) AS alias`.
-    -- Squeal's direct join combinators don't alias the *result* of the join.
-    -- This would require wrapping the join in a subquery.
-    -- For now, we'll fail if an alias is applied to a complex join structure directly.
-    -- Simple table references with aliases are handled by RelationExprTableRef.
-    case maybeAliasClause of
-      Just _ ->
-        fail
-          "Aliasing a JOIN clause directly is not supported. Consider a subquery: (SELECT * FROM ...) AS alias"
-      Nothing -> renderPGTJoinedTable joinedTable
-  -- PGT_AST.InParensTableRefTableRef was an incorrect pattern, removing it.
-  -- Parenthesized joins are handled by PGT_AST.InParensJoinedTable within renderPGTJoinedTable.
-  unsupported ->
-    fail $ "Unsupported TableRef type in renderSingleTableRef: " <> show unsupported
-
-
-renderPGTJoinedTable :: PGT_AST.JoinedTable -> Q Exp
-renderPGTJoinedTable = \case
-  PGT_AST.InParensJoinedTable joinedTable -> renderPGTJoinedTable joinedTable
-  PGT_AST.MethJoinedTable joinMeth leftRef rightRef -> do
-    leftTableExp <- renderSingleTableRef leftRef
-    rightTableExp <- renderSingleTableRef rightRef
-    case joinMeth of
-      PGT_AST.QualJoinMeth maybeJoinType joinQual ->
-        case joinQual of
-          PGT_AST.OnJoinQual onConditionAExpr -> do
-            onConditionExp <- renderPGTAExpr onConditionAExpr
-            squealJoinFn <-
-              case maybeJoinType of
-                Just (PGT_AST.LeftJoinType _) -> pure $ VarE 'S.leftOuterJoin
-                Just (PGT_AST.RightJoinType _) -> pure $ VarE 'S.rightOuterJoin
-                Just (PGT_AST.FullJoinType _) -> pure $ VarE 'S.fullOuterJoin
-                Just PGT_AST.InnerJoinType -> pure $ VarE 'S.innerJoin
-                Nothing -> pure $ VarE 'S.innerJoin -- SQL JOIN (no type) is INNER JOIN
-                -- Change: Use S.& for join: leftTableExp & squealJoinFn rightTableExp onConditionExp
-            pure $
-              InfixE
-                (Just leftTableExp)
-                (VarE '(S.&))
-                (Just (squealJoinFn `AppE` rightTableExp `AppE` onConditionExp))
-          PGT_AST.UsingJoinQual _identsNE ->
-            fail "USING join qualification not yet supported"
-      PGT_AST.CrossJoinMeth ->
-        -- Change: Use S.& for crossJoin: leftTableExp & S.crossJoin rightTableExp
-        pure $
-          InfixE
-            (Just leftTableExp)
-            (VarE '(S.&))
-            (Just (VarE 'S.crossJoin `AppE` rightTableExp))
-      PGT_AST.NaturalJoinMeth _naturalJoinType ->
-        -- Squeal does not have direct high-level support for NATURAL JOIN.
-        -- These would typically be rewritten as INNER JOINs with USING clauses
-        -- or explicit ON conditions based on common column names.
-        -- This is complex to implement correctly in the QQ and might be error-prone.
-        fail "NATURAL JOIN is not supported by Squeal-QQ."
-
-
-renderPGTRelationExprTableRef
-  :: PGT_AST.RelationExpr -> Maybe PGT_AST.AliasClause -> Q Exp
-renderPGTRelationExprTableRef relationExpr maybeAliasClause = do
-  tableExpr <-
-    case relationExpr of
-      PGT_AST.SimpleRelationExpr qualifiedName isAsterisk -> do
-        when isAsterisk $ fail "Relation with '*' (e.g. 'table *') is not supported."
-        renderPGTQualifiedName qualifiedName
-      PGT_AST.OnlyRelationExpr _qualifiedName _areParensPresent -> do
-        -- Squeal doesn't have a direct equivalent for ONLY, so we treat it as a normal table for now.
-        -- This might need adjustment if ONLY semantics are critical.
-        fail "ONLY keyword is not supported."
-
-  aliasStr <-
-    case maybeAliasClause of
-      Just (PGT_AST.AliasClause _ aliasIdent _) -> pure $ Text.unpack (getIdentText aliasIdent)
-      Nothing -> case relationExpr of -- Infer default alias if none provided
-        PGT_AST.SimpleRelationExpr (PGT_AST.SimpleQualifiedName ident) _ -> pure $ Text.unpack (getIdentText ident)
-        PGT_AST.SimpleRelationExpr
-          ( PGT_AST.IndirectedQualifiedName
-              _
-              (NE.last -> PGT_AST.AttrNameIndirectionEl ident)
-            )
-          _ -> pure $ Text.unpack (getIdentText ident)
-        _ ->
-          fail $
-            "Cannot determine default alias for relation expression: " <> show relationExpr
-
-  pure $ VarE 'S.table `AppE` (VarE 'S.as `AppE` tableExpr `AppE` LabelE aliasStr)
-
-
-renderPGTQualifiedName :: PGT_AST.QualifiedName -> Q Exp
-renderPGTQualifiedName = \case
-  PGT_AST.SimpleQualifiedName ident -> pure $ LabelE (Text.unpack (getIdentText ident))
-  PGT_AST.IndirectedQualifiedName
-    schemaIdent
-    (PGT_AST.AttrNameIndirectionEl colIdent NE.:| []) ->
-      -- Assuming schema.table.col
-      pure $
-        VarE '(S.!)
-          `AppE` LabelE (Text.unpack (getIdentText schemaIdent))
-          `AppE` LabelE (Text.unpack (getIdentText colIdent))
-  unsupported ->
-    fail $ "Unsupported qualified name for table reference: " <> show unsupported
-
-
--- | Defines associativity of an operator.
-data Associativity = LeftAssoc | RightAssoc | NonAssoc
-  deriving stock (Eq, Show)
-
-
--- | Holds details for a binary operator relevant to precedence restructuring.
-data OperatorDetails = OperatorDetails
-  { odConstructor :: PGT_AST.AExpr -> PGT_AST.AExpr -> PGT_AST.AExpr
-  , odPrecedence :: Int
-  , odAssociativity :: Associativity
-  }
-
-
-{- | Extracts components if the expression is a recognized binary operator.
-Higher precedence number means binds tighter.
-Based on PostgreSQL operator precedence.
--}
-getOperatorDetails
-  :: PGT_AST.AExpr -> Maybe (PGT_AST.AExpr, OperatorDetails, PGT_AST.AExpr)
-getOperatorDetails = \case
-  PGT_AST.SymbolicBinOpAExpr l symOp r ->
-    let
-      details _op constr prec assoc = Just (l, OperatorDetails constr prec assoc, r)
-      mathDetails mathOp prec assoc =
-        details
-          (PGT_AST.MathSymbolicExprBinOp mathOp)
-          ( \l' r' -> PGT_AST.SymbolicBinOpAExpr l' (PGT_AST.MathSymbolicExprBinOp mathOp) r'
-          )
-          prec
-          assoc
-    in
-      case symOp of
-        PGT_AST.MathSymbolicExprBinOp PGT_AST.ArrowUpMathOp -> mathDetails PGT_AST.ArrowUpMathOp 8 LeftAssoc
-        -- \^ (exponentiation)
-        PGT_AST.MathSymbolicExprBinOp op
-          | op `elem` [PGT_AST.AsteriskMathOp, PGT_AST.SlashMathOp, PGT_AST.PercentMathOp] ->
-              mathDetails op 7 LeftAssoc
-        -- \* / %
-        PGT_AST.MathSymbolicExprBinOp op
-          | op `elem` [PGT_AST.PlusMathOp, PGT_AST.MinusMathOp] ->
-              mathDetails op 6 LeftAssoc -- binary + -
-        PGT_AST.MathSymbolicExprBinOp op -- Comparisons
-          | op
-              `elem` [ PGT_AST.ArrowLeftMathOp
-                     , PGT_AST.ArrowRightMathOp
-                     , PGT_AST.EqualsMathOp
-                     , PGT_AST.LessEqualsMathOp
-                     , PGT_AST.GreaterEqualsMathOp
-                     , PGT_AST.ArrowLeftArrowRightMathOp
-                     , PGT_AST.ExclamationEqualsMathOp
-                     ] ->
-              mathDetails op 3 LeftAssoc -- < > = <= >= <> !=
-        PGT_AST.QualSymbolicExprBinOp qualOp ->
-          -- User-defined operators, bitwise, etc.
-          details
-            (PGT_AST.QualSymbolicExprBinOp qualOp)
-            ( \l' r' -> PGT_AST.SymbolicBinOpAExpr l' (PGT_AST.QualSymbolicExprBinOp qualOp) r'
-            )
-            5
-            LeftAssoc
-        _ -> Nothing -- Should be exhaustive for PGT_AST.MathSymbolicExprBinOp if it's a binary op
-  PGT_AST.AndAExpr l r -> Just (l, OperatorDetails PGT_AST.AndAExpr 2 LeftAssoc, r) -- AND (precedence 2 in PG docs example)
-  PGT_AST.OrAExpr l r -> Just (l, OperatorDetails PGT_AST.OrAExpr 1 LeftAssoc, r) -- OR (precedence 1 in PG docs example)
-  PGT_AST.VerbalExprBinOpAExpr l notOp verbalOp r mEscape ->
-    -- LIKE, ILIKE, SIMILAR TO
-    Just
-      ( l
-      , OperatorDetails
-          (\l' r' -> PGT_AST.VerbalExprBinOpAExpr l' notOp verbalOp r' mEscape)
-          3
-          LeftAssoc
-      , r -- Same as comparisons
-      )
-  PGT_AST.ReversableOpAExpr l notOp (PGT_AST.DistinctFromAExprReversableOp r) ->
-    -- IS DISTINCT FROM
-    Just
-      ( l
-      , OperatorDetails
-          ( \l' r' ->
-              PGT_AST.ReversableOpAExpr l' notOp (PGT_AST.DistinctFromAExprReversableOp r')
-          )
-          3
-          LeftAssoc
-      , r -- Same as =
-      )
-  _ -> Nothing
-
-
--- | Rearranges the AExpr syntax tree to account for operator precedence.
-fixOperatorPrecedence :: PGT_AST.AExpr -> PGT_AST.AExpr
-fixOperatorPrecedence = go
-  where
-    go expr =
-      case getOperatorDetails expr of
-        Just (l1, op1Details, r1) ->
-          let
-            l1Fixed = go l1
-            r1Fixed = go r1
-            currentOpConstructor = odConstructor op1Details
-            currentPrecedence = odPrecedence op1Details
-            currentAssociativity = odAssociativity op1Details
-          in
-            case getOperatorDetails r1Fixed of
-              Just (l2, op2Details, r2) ->
-                let
-                  -- We have effectively: l1Fixed `op1` (l2 `op2` r2)
-                  -- l2 is the left child of the (potentially restructured) r1Fixed
-                  -- r2 is the right child of the (potentially restructured) r1Fixed
-                  innerOpConstructor = odConstructor op2Details
-                  innerPrecedence = odPrecedence op2Details
-                in
-                  -- innerAssociativity = odAssociativity op2Details -- Not used in this branch's logic directly
-
-                  if currentPrecedence < innerPrecedence
-                    || (currentPrecedence == innerPrecedence && currentAssociativity == RightAssoc)
-                    then
-                      -- op2 binds tighter, or op1 is right-associative with same precedence.
-                      -- Structure l1Fixed `op1` (l2 `op2` r2) is correct.
-                      currentOpConstructor l1Fixed r1Fixed
-                    else
-                      -- op1 binds tighter, or op1 is left-associative with same precedence.
-                      -- We need to rotate to form: (l1Fixed `op1` l2) `op2` r2
-                      let
-                        newLeftChild = currentOpConstructor l1Fixed l2
-                      in
-                        go (innerOpConstructor newLeftChild r2) -- Recursively fix the new structure
-              Nothing ->
-                -- Right child r1Fixed is not a binary operator we're rebalancing.
-                -- The structure l1Fixed `op1` r1Fixed is locally correct.
-                currentOpConstructor l1Fixed r1Fixed
-        Nothing ->
-          -- Current expression `expr` is not a binary operator handled by getOperatorDetails,
-          -- or it's an atom. Recursively fix its children.
-          case expr of
-            PGT_AST.CExprAExpr c -> PGT_AST.CExprAExpr c -- CExprs are atoms or structured (FuncCExpr, CaseCExpr etc.)
-            PGT_AST.TypecastAExpr e t -> PGT_AST.TypecastAExpr (go e) t
-            PGT_AST.CollateAExpr e c -> PGT_AST.CollateAExpr (go e) c
-            PGT_AST.AtTimeZoneAExpr e1 e2 -> PGT_AST.AtTimeZoneAExpr (go e1) (go e2)
-            PGT_AST.PlusAExpr e -> PGT_AST.PlusAExpr (go e) -- Unary plus
-            -- MinusAExpr is handled by fixOperatorPrecedence if it's part of a binary op,
-            -- otherwise it's a unary negate.
-            PGT_AST.MinusAExpr e -> PGT_AST.MinusAExpr (go e)
-            PGT_AST.PrefixQualOpAExpr op e -> PGT_AST.PrefixQualOpAExpr op (go e)
-            PGT_AST.SuffixQualOpAExpr e op -> PGT_AST.SuffixQualOpAExpr (go e) op
-            PGT_AST.NotAExpr e -> PGT_AST.NotAExpr (go e)
-            PGT_AST.ReversableOpAExpr e notFlag revOp ->
-              let
-                eFixed = go e
-              in
-                case revOp of
-                  PGT_AST.DistinctFromAExprReversableOp{} -> expr -- Should have been caught by getOperatorDetails
-                  PGT_AST.BetweenAExprReversableOp symm bExpr aExpr ->
-                    PGT_AST.ReversableOpAExpr
-                      eFixed
-                      notFlag
-                      (PGT_AST.BetweenAExprReversableOp symm (goBExpr bExpr) (go aExpr))
-                  PGT_AST.InAExprReversableOp inExpr ->
-                    PGT_AST.ReversableOpAExpr
-                      eFixed
-                      notFlag
-                      (PGT_AST.InAExprReversableOp (goInExpr inExpr))
-                  _ -> PGT_AST.ReversableOpAExpr eFixed notFlag revOp -- For IS NULL, IS TRUE etc.
-            PGT_AST.IsnullAExpr e -> PGT_AST.IsnullAExpr (go e)
-            PGT_AST.NotnullAExpr e -> PGT_AST.NotnullAExpr (go e)
-            PGT_AST.OverlapsAExpr row1 row2 -> PGT_AST.OverlapsAExpr (goRow row1) (goRow row2)
-            PGT_AST.SubqueryAExpr e op st sub ->
-              PGT_AST.SubqueryAExpr
-                (go e)
-                op
-                st
-                (either (Left . goSelectWithParens) (Right . go) sub)
-            PGT_AST.UniqueAExpr s -> PGT_AST.UniqueAExpr (goSelectWithParens s)
-            PGT_AST.DefaultAExpr -> PGT_AST.DefaultAExpr
-            _ -> expr -- Leaf node or unhandled construct
-    goBExpr :: PGT_AST.BExpr -> PGT_AST.BExpr
-    goBExpr = \case
-      PGT_AST.CExprBExpr c -> PGT_AST.CExprBExpr c
-      PGT_AST.TypecastBExpr be t -> PGT_AST.TypecastBExpr (goBExpr be) t
-      PGT_AST.PlusBExpr be -> PGT_AST.PlusBExpr (goBExpr be)
-      PGT_AST.MinusBExpr be -> PGT_AST.MinusBExpr (goBExpr be)
-      -- BExpr's own binary ops are typically higher precedence than AExpr's,
-      -- but for completeness, one could define getOperatorDetails for BExpr too.
-      -- For now, just recurse.
-      PGT_AST.SymbolicBinOpBExpr l op r -> PGT_AST.SymbolicBinOpBExpr (goBExpr l) op (goBExpr r)
-      PGT_AST.QualOpBExpr op be -> PGT_AST.QualOpBExpr op (goBExpr be)
-      PGT_AST.IsOpBExpr be notFlag isOp ->
-        let
-          beFixed = goBExpr be
-        in
-          case isOp of
-            PGT_AST.DistinctFromBExprIsOp b ->
-              PGT_AST.IsOpBExpr beFixed notFlag (PGT_AST.DistinctFromBExprIsOp (goBExpr b))
-            _ -> PGT_AST.IsOpBExpr beFixed notFlag isOp
-
-    goRow :: PGT_AST.Row -> PGT_AST.Row
-    goRow = \case
-      PGT_AST.ExplicitRowRow mExprs -> PGT_AST.ExplicitRowRow (fmap (NE.map go) mExprs)
-      PGT_AST.ImplicitRowRow (PGT_AST.ImplicitRow exprs aexpr) -> PGT_AST.ImplicitRowRow (PGT_AST.ImplicitRow (NE.map go exprs) (go aexpr))
-
-    goSelectWithParens :: PGT_AST.SelectWithParens -> PGT_AST.SelectWithParens
-    goSelectWithParens = id -- Placeholder: A full traversal would be needed.
-    goInExpr :: PGT_AST.InExpr -> PGT_AST.InExpr
-    goInExpr = \case
-      PGT_AST.SelectInExpr s -> PGT_AST.SelectInExpr (goSelectWithParens s)
-      PGT_AST.ExprListInExpr exprs -> PGT_AST.ExprListInExpr (NE.map go exprs)
-
-
-renderPGTAExpr :: PGT_AST.AExpr -> Q Exp
-renderPGTAExpr astExpr = case fixOperatorPrecedence astExpr of
-  PGT_AST.CExprAExpr cExpr -> renderPGTCExpr cExpr
-  PGT_AST.TypecastAExpr aExpr typename -> do
-    tnExp <- renderPGTTypename typename
-    aExp <- renderPGTAExpr aExpr
-    pure $ VarE 'S.cast `AppE` tnExp `AppE` aExp
-  PGT_AST.SymbolicBinOpAExpr left op right -> do
-    lExp <- renderPGTAExpr left
-    rExp <- renderPGTAExpr right
-    squealOpExp <-
-      case op of
-        PGT_AST.MathSymbolicExprBinOp mathOp -> pure $ renderPGTMathOp mathOp
-        PGT_AST.QualSymbolicExprBinOp qualOp -> pure $ renderPGTQualOp qualOp
-    pure (squealOpExp `AppE` lExp `AppE` rExp)
-  PGT_AST.PrefixQualOpAExpr op expr -> do
-    let
-      opExp' = renderPGTQualOp op
-    eExp' <- renderPGTAExpr expr
-    pure (opExp' `AppE` eExp')
-  PGT_AST.AndAExpr left right -> do
-    lExp' <- renderPGTAExpr left
-    rExp' <- renderPGTAExpr right
-    pure (VarE '(S..&&) `AppE` lExp' `AppE` rExp')
-  PGT_AST.OrAExpr left right -> do
-    lExp' <- renderPGTAExpr left
-    rExp' <- renderPGTAExpr right
-    pure (VarE '(S..||) `AppE` lExp' `AppE` rExp')
-  PGT_AST.NotAExpr expr -> do
-    eExp' <- renderPGTAExpr expr
-    pure (VarE 'S.not_ `AppE` eExp')
-  PGT_AST.VerbalExprBinOpAExpr left not op right mEscape -> do
-    when (isJust mEscape) $ fail "LIKE with ESCAPE is not supported yet."
-    lExp' <- renderPGTAExpr left
-    rExp' <- renderPGTAExpr right
-    baseOpExp <-
-      case op of
-        PGT_AST.LikeVerbalExprBinOp -> pure $ VarE 'S.like
-        PGT_AST.IlikeVerbalExprBinOp -> pure $ VarE 'S.ilike
-        _ -> fail $ "Unsupported verbal binary operator: " <> show op
-    let
-      finalOpExp = if not then VarE 'S.not_ `AppE` baseOpExp else baseOpExp
-    pure (finalOpExp `AppE` lExp' `AppE` rExp')
-  PGT_AST.ReversableOpAExpr expr not reversableOp -> do
-    renderedExpr' <- renderPGTAExpr expr
-    case reversableOp of
-      PGT_AST.NullAExprReversableOp ->
-        pure $ (if not then VarE 'S.isNotNull else VarE 'S.isNull) `AppE` renderedExpr'
-      PGT_AST.BetweenAExprReversableOp _asymmetric bExpr andAExpr -> do
-        bExp' <- renderPGTBExpr bExpr
-        aExp' <- renderPGTAExpr andAExpr
-        let
-          opVar' = if not then VarE 'S.notBetween else VarE 'S.between
-        pure $ opVar' `AppE` renderedExpr' `AppE` TupE [Just bExp', Just aExp']
-      PGT_AST.InAExprReversableOp inExpr ->
-        let
-          opVar' = if not then VarE 'S.notIn else VarE 'S.in_
-        in
-          case inExpr of
-            PGT_AST.ExprListInExpr exprList -> do
-              listExp' <- ListE <$> mapM renderPGTAExpr (NE.toList exprList)
-              pure $ opVar' `AppE` renderedExpr' `AppE` listExp'
-            _ -> fail "Unsupported IN subquery expression"
-      _ -> fail $ "Unsupported reversable operator: " <> show reversableOp
-  PGT_AST.DefaultAExpr -> pure $ ConE 'S.Default
-  PGT_AST.MinusAExpr expr -> do
-    -- Unary minus
-    eExp' <- renderPGTAExpr expr
-    let
-      zeroExp = AppE (VarE 'fromInteger) (LitE (IntegerL 0))
-    pure (InfixE (Just zeroExp) (VarE '(-)) (Just eExp'))
-  unsupported -> fail $ "Unsupported AExpr: " <> show unsupported
-
-
-renderPGTBExpr :: PGT_AST.BExpr -> Q Exp
-renderPGTBExpr = \case
-  PGT_AST.CExprBExpr cExpr -> renderPGTCExpr cExpr
-  PGT_AST.TypecastBExpr bExpr typename -> do
-    tnExp <- renderPGTTypename typename
-    bExp <- renderPGTBExpr bExpr
-    pure $ VarE 'S.cast `AppE` tnExp `AppE` bExp
-  PGT_AST.SymbolicBinOpBExpr left op right -> do
-    lExp <- renderPGTBExpr left
-    rExp <- renderPGTBExpr right
-    squealOpExp <-
-      case op of
-        PGT_AST.MathSymbolicExprBinOp mathOp -> pure $ renderPGTMathOp mathOp
-        PGT_AST.QualSymbolicExprBinOp qualOp -> pure $ renderPGTQualOp qualOp
-    pure (squealOpExp `AppE` lExp `AppE` rExp)
-  unsupported -> fail $ "Unsupported BExpr: " <> show unsupported
-
-
-renderPGTCExpr :: PGT_AST.CExpr -> Q Exp
-renderPGTCExpr = \case
-  PGT_AST.AexprConstCExpr aexprConst -> pure $ renderPGTAexprConst aexprConst
-  PGT_AST.ColumnrefCExpr columnref -> pure $ renderPGTColumnref columnref
-  PGT_AST.ParamCExpr n maybeIndirection -> do
-    when (isJust maybeIndirection) $
-      fail "Parameters with indirection (e.g. $1[i]) are not supported."
-    pure $ VarE 'S.param `AppTypeE` LitT (NumTyLit (fromIntegral n))
-  PGT_AST.InParensCExpr expr maybeIndirection -> do
-    when (isJust maybeIndirection) $
-      fail "Parenthesized expressions with indirection are not supported."
-    renderPGTAExpr expr -- Squeal's operator precedence should handle this
-  PGT_AST.FuncCExpr funcExpr -> renderPGTFuncExpr funcExpr
-  unsupported -> fail $ "Unsupported CExpr: " <> show unsupported
-
-
-renderPGTFuncExpr :: PGT_AST.FuncExpr -> Q Exp
-renderPGTFuncExpr = \case
-  PGT_AST.ApplicationFuncExpr funcApp maybeWithinGroup maybeFilter maybeOver -> do
-    when (isJust maybeWithinGroup) $ fail "WITHIN GROUP clause is not supported."
-    when (isJust maybeFilter) $ fail "FILTER clause is not supported."
-    when (isJust maybeOver) $ fail "OVER clause is not supported."
-    renderPGTFuncApplication funcApp
-  PGT_AST.SubexprFuncExpr funcCommonSubexpr -> renderPGTFuncExprCommonSubexpr funcCommonSubexpr
-
-
-renderPGTFuncApplication :: PGT_AST.FuncApplication -> Q Exp
-renderPGTFuncApplication (PGT_AST.FuncApplication funcName maybeParams) =
-  case funcName of
-    PGT_AST.IndirectedFuncName{} ->
-      fail "Functions with indirection (e.g. schema.func) are not supported."
-    PGT_AST.TypeFuncName fident ->
-      let
-        fnNameStr = Text.unpack (getIdentText fident)
-      in
-        case Text.toLower (Text.pack fnNameStr) of
-          "inline" ->
-            case maybeParams of
-              Just (PGT_AST.NormalFuncApplicationParams _ args _) ->
-                case NE.toList args of
-                  [ PGT_AST.ExprFuncArgExpr
-                      (PGT_AST.CExprAExpr (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing)))
-                    ] -> do
-                      let
-                        varName :: Name
-                        varName = mkName . Text.unpack . getIdentText $ ident
-                      pure $ VarE 'S.inline `AppE` VarE varName
-                  _ -> fail "inline() function expects a single variable argument"
-              _ -> fail "inline() function expects a single variable argument"
-          "inline_param" ->
-            case maybeParams of
-              Just (PGT_AST.NormalFuncApplicationParams _ args _) ->
-                case NE.toList args of
-                  [ PGT_AST.ExprFuncArgExpr
-                      (PGT_AST.CExprAExpr (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing)))
-                    ] -> do
-                      let
-                        varName :: Name
-                        varName = mkName . Text.unpack . getIdentText $ ident
-                      pure $ VarE 'S.inlineParam `AppE` VarE varName
-                  _ -> fail "inline_param() function expects a single variable argument"
-              _ -> fail "inline_param() function expects a single variable argument"
-          otherFnName ->
-            let
-              squealFn :: Q Exp
-              squealFn =
-                case otherFnName of
-                  "coalesce" -> pure $ VarE 'S.coalesce
-                  "lower" -> pure $ VarE 'S.lower
-                  "char_length" -> pure $ VarE 'S.charLength
-                  "character_length" -> pure $ VarE 'S.charLength
-                  "upper" -> pure $ VarE 'S.upper
-                  "count" -> pure $ VarE 'S.count -- Special handling for count(*) might be needed
-                  "now" -> pure $ VarE 'S.now
-                  _ -> fail $ "Unsupported function: " <> fnNameStr
-            in
-              case maybeParams of
-                Nothing -> squealFn -- No-argument function
-                Just params -> case params of
-                  PGT_AST.NormalFuncApplicationParams maybeAllOrDistinct args maybeSortClause -> do
-                    when (isJust maybeAllOrDistinct) $
-                      fail "DISTINCT in function calls is not supported."
-                    when (isJust maybeSortClause) $
-                      fail "ORDER BY in function calls is not supported."
-                    fn <- squealFn
-                    argExps <- mapM renderPGTFuncArgExpr (NE.toList args)
-                    pure $ foldl' AppE fn argExps
-                  PGT_AST.StarFuncApplicationParams ->
-                    -- Specific for count(*)
-                    if fnNameStr == "count"
-                      then pure $ VarE 'S.countStar
-                      else fail "Star argument only supported for COUNT"
-                  _ -> fail $ "Unsupported function parameters structure: " <> show params
-
-
-renderPGTFuncArgExpr :: PGT_AST.FuncArgExpr -> Q Exp
-renderPGTFuncArgExpr = \case
-  PGT_AST.ExprFuncArgExpr aExpr -> renderPGTAExpr aExpr
-  _ -> fail "Named or colon-syntax function arguments not supported"
-
-
-renderPGTFuncExprCommonSubexpr :: PGT_AST.FuncExprCommonSubexpr -> Q Exp
-renderPGTFuncExprCommonSubexpr = \case
-  PGT_AST.CurrentTimestampFuncExprCommonSubexpr (Just _) ->
-    fail "CURRENT_TIMESTAMP with precision is not supported."
-  PGT_AST.CurrentTimestampFuncExprCommonSubexpr Nothing -> pure $ VarE 'S.now -- Or S.currentTimestamp
-  PGT_AST.CurrentDateFuncExprCommonSubexpr -> pure $ VarE 'S.currentDate
-  PGT_AST.CoalesceFuncExprCommonSubexpr exprListNE -> do
-    renderedInitExprs <- mapM renderPGTAExpr (NE.init exprListNE)
-    renderedLastExpr <- renderPGTAExpr (NE.last exprListNE)
-    pure $ VarE 'S.coalesce `AppE` ListE renderedInitExprs `AppE` renderedLastExpr
-  e -> fail $ "Unsupported common function subexpression: " <> show e
-
-
-renderPGTColumnref :: PGT_AST.Columnref -> Exp
-renderPGTColumnref (PGT_AST.Columnref colId maybeIndirection) =
-    case maybeIndirection of
-      Nothing -> LabelE (Text.unpack (getIdentText colId))
-      Just indirection ->
-        let
-          base = LabelE (Text.unpack (getIdentText colId))
-        in
-          foldl' applyIndirection base (NE.toList indirection)
-  where
-    applyIndirection acc = \case
-      PGT_AST.AttrNameIndirectionEl attrName ->
-        VarE '(S.!) `AppE` acc `AppE` LabelE (Text.unpack (getIdentText attrName))
-      _ -> error "Unsupported column reference indirection"
-
-
-renderPGTAexprConst :: PGT_AST.AexprConst -> Exp
-renderPGTAexprConst = \case
-  PGT_AST.IAexprConst n ->
-    ConE 'S.UnsafeExpression
-      `AppE` ( VarE 'BS8.pack
-                 `AppE` LitE (StringL (show n))
-             )
-  PGT_AST.FAexprConst f ->
-    ConE 'S.UnsafeExpression
-      `AppE` ( VarE 'BS8.pack
-                 `AppE` LitE (StringL (show f))
-             )
-  PGT_AST.SAexprConst s ->
-    VarE 'fromString `AppE` LitE (StringL (Text.unpack s))
-  PGT_AST.BoolAexprConst True -> VarE 'S.true
-  PGT_AST.BoolAexprConst False -> VarE 'S.false
-  PGT_AST.NullAexprConst -> VarE 'S.null_
-  unsupported -> error $ "Unsupported AexprConst: " <> show unsupported
-
-
-renderPGTMathOp :: PGT_AST.MathOp -> Exp
-renderPGTMathOp = \case
-  PGT_AST.PlusMathOp -> VarE '(+)
-  PGT_AST.MinusMathOp -> VarE '(-)
-  PGT_AST.AsteriskMathOp -> VarE '(*)
-  PGT_AST.EqualsMathOp -> VarE '(S..==)
-  PGT_AST.ArrowLeftArrowRightMathOp -> VarE '(S../=) -- <>
-  PGT_AST.ExclamationEqualsMathOp -> VarE '(S../=) -- !=
-  PGT_AST.ArrowRightMathOp -> VarE '(S..>)
-  PGT_AST.GreaterEqualsMathOp -> VarE '(S..>=)
-  PGT_AST.ArrowLeftMathOp -> VarE '(S..<)
-  PGT_AST.LessEqualsMathOp -> VarE '(S..<=)
-  _ -> error "Unsupported math operator"
-
-
-renderPGTQualOp :: PGT_AST.QualOp -> Exp
-renderPGTQualOp = \case
-  PGT_AST.OpQualOp opText ->
-    case Text.toLower opText of
-      "+" -> VarE '(+)
-      "-" -> VarE '(-)
-      "*" -> VarE '(*)
-      "=" -> VarE '(S..==)
-      "<>" -> VarE '(S../=)
-      "!=" -> VarE '(S../=)
-      ">" -> VarE '(S..>)
-      ">=" -> VarE '(S..>=)
-      "<" -> VarE '(S..<)
-      "<=" -> VarE '(S..<=)
-      "and" -> VarE '(S..&&)
-      "or" -> VarE '(S..||)
-      "not" -> VarE 'S.not_
-      "like" -> VarE 'S.like
-      "ilike" -> VarE 'S.ilike
-      _ -> error $ "Unsupported QualOp operator text: " <> Text.unpack opText
-  PGT_AST.OperatorQualOp _anyOperator ->
-    error "OPERATOR(any_operator) syntax not supported"
-
-
-renderPGTTypename :: PGT_AST.Typename -> Q Exp
-renderPGTTypename (PGT_AST.Typename setof simpleTypename nullable arrayInfo) = do
-  when setof $ fail "SETOF type modifier is not supported."
-  when nullable $ fail "Nullable type modifier '?' is not supported."
-  baseTypeExp <- renderPGTSimpleTypename simpleTypename
-  case arrayInfo of
-    Nothing -> pure baseTypeExp
-    Just (dims, nullableArray) -> do
-      when nullableArray $ fail "Nullable array modifier '?' is not supported."
-      renderPGTArrayDimensions baseTypeExp dims
-
-
-renderPGTArrayDimensions :: Exp -> PGT_AST.TypenameArrayDimensions -> Q Exp
-renderPGTArrayDimensions baseTypeExp = \case
-  PGT_AST.BoundsTypenameArrayDimensions bounds ->
-    -- Squeal's fixarray takes a type-level list of Nats for dimensions.
-    -- This is hard to represent directly from parsed integer bounds.
-    -- For now, we'll only support 1D arrays if bounds are provided.
-    case NE.toList bounds of
-      [Just dim] ->
-        pure $
-          VarE 'S.fixarray
-            `AppTypeE` LitT (NumTyLit (fromIntegral dim))
-            `AppE` baseTypeExp
-      [_] -> pure $ VarE 'S.vararray `AppE` baseTypeExp -- e.g. int[]
-      _ ->
-        fail "Multidimensional arrays with explicit bounds not yet supported"
-  PGT_AST.ExplicitTypenameArrayDimensions Nothing -> pure $ VarE 'S.vararray `AppE` baseTypeExp -- e.g. sometype ARRAY
-  PGT_AST.ExplicitTypenameArrayDimensions (Just dim) ->
-    pure $
-      VarE 'S.fixarray
-        `AppTypeE` LitT (NumTyLit (fromIntegral dim))
-        `AppE` baseTypeExp -- e.g. sometype ARRAY[N]
-
-
-renderPGTSimpleTypename :: PGT_AST.SimpleTypename -> Q Exp
-renderPGTSimpleTypename = \case
-  PGT_AST.GenericTypeSimpleTypename
-    (PGT_AST.GenericType typeFnName attrs maybeModifiers) -> do
-      when (isJust attrs) $
-        fail "Qualified type names (e.g. schema.my_type) are not supported."
-      let
-        nameLower = Text.toLower (getIdentText typeFnName)
-        extractLength :: Maybe PGT_AST.TypeModifiers -> Q Integer
-        extractLength = \case
-          Just
-            ((PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) NE.:| []) -> pure (fromIntegral n)
-          Just other ->
-            fail $
-              "Unsupported type modifier for " <> Text.unpack nameLower <> ": " <> show other
-          Nothing ->
-            fail $
-              "Type "
-                <> Text.unpack nameLower
-                <> " requires a length argument (e.g., "
-                <> Text.unpack nameLower
-                <> "(N))."
-
-        extractLengthOrDefault :: Integer -> Maybe PGT_AST.TypeModifiers -> Q Integer
-        extractLengthOrDefault def = \case
-          Just
-            ((PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) NE.:| []) -> pure (fromIntegral n)
-          Just other ->
-            fail $
-              "Unsupported type modifier for " <> Text.unpack nameLower <> ": " <> show other
-          Nothing -> pure def
-      case nameLower of
-        "char" -> do
-          len <- extractLengthOrDefault 1 maybeModifiers
-          pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit len)
-        "character" -> do
-          len <- extractLengthOrDefault 1 maybeModifiers
-          pure $ VarE 'S.character `AppTypeE` LitT (NumTyLit len)
-        "varchar" -> case maybeModifiers of
-          Nothing -> pure $ VarE 'S.text -- varchar without length is text
-          Just _ -> do
-            len <- extractLength maybeModifiers
-            pure $ VarE 'S.varchar `AppTypeE` LitT (NumTyLit len)
-        "character varying" -> case maybeModifiers of
-          Nothing -> pure $ VarE 'S.text -- character varying without length is text
-          Just _ -> do
-            len <- extractLength maybeModifiers
-            pure $ VarE 'S.characterVarying `AppTypeE` LitT (NumTyLit len)
-        "bool" -> pure $ VarE 'S.bool
-        "int2" -> pure $ VarE 'S.int2
-        "smallint" -> pure $ VarE 'S.smallint
-        "int4" -> pure $ VarE 'S.int4
-        "int" -> pure $ VarE 'S.int
-        "integer" -> pure $ VarE 'S.integer
-        "int8" -> pure $ VarE 'S.int8
-        "bigint" -> pure $ VarE 'S.bigint
-        "numeric" -> pure $ VarE 'S.numeric -- Ignoring precision/scale for now
-        "float4" -> pure $ VarE 'S.float4 -- Ignoring precision for now
-        "real" -> pure $ VarE 'S.real
-        "float8" -> pure $ VarE 'S.float8
-        "double precision" -> pure $ VarE 'S.doublePrecision
-        "money" -> pure $ VarE 'S.money
-        "text" -> pure $ VarE 'S.text
-        "bytea" -> pure $ VarE 'S.bytea
-        "timestamp" -> pure $ VarE 'S.timestamp
-        "timestamptz" -> pure $ VarE 'S.timestamptz
-        "timestamp with time zone" -> pure $ VarE 'S.timestampWithTimeZone
-        "date" -> pure $ VarE 'S.date
-        "time" -> pure $ VarE 'S.time
-        "timetz" -> pure $ VarE 'S.timetz
-        "time with time zone" -> pure $ VarE 'S.timeWithTimeZone
-        "interval" -> pure $ VarE 'S.interval
-        "uuid" -> pure $ VarE 'S.uuid
-        "inet" -> pure $ VarE 'S.inet
-        "json" -> pure $ VarE 'S.json
-        "jsonb" -> pure $ VarE 'S.jsonb
-        "tsvector" -> pure $ VarE 'S.tsvector
-        "tsquery" -> pure $ VarE 'S.tsquery
-        "oid" -> pure $ VarE 'S.oid
-        "int4range" -> pure $ VarE 'S.int4range
-        "int8range" -> pure $ VarE 'S.int8range
-        "numrange" -> pure $ VarE 'S.numrange
-        "tsrange" -> pure $ VarE 'S.tsrange
-        "tstzrange" -> pure $ VarE 'S.tstzrange
-        "daterange" -> pure $ VarE 'S.daterange
-        "record" -> pure $ VarE 'S.record
-        other -> fail $ "Unsupported generic type name: " <> Text.unpack other
-  PGT_AST.NumericSimpleTypename numeric -> renderPGTNumeric numeric
-  PGT_AST.BitSimpleTypename (PGT_AST.Bit _varying _maybeLength) ->
-    -- PostgreSQL's BIT type without length is BIT(1). BIT VARYING without length is unlimited.
-    -- Squeal's `char` and `varchar` are for text, not bit strings.
-    -- Squeal does not have a direct equivalent for PG bit string types yet.
-    -- Potentially map to bytea or text, or add new Squeal types. For now, error.
-    fail
-      "BIT and BIT VARYING types are not directly supported by Squeal's `char`/`varchar` like types. Consider using bytea or text, or a custom Squeal type."
-  PGT_AST.CharacterSimpleTypename charTypeAst ->
-    case charTypeAst of
-      PGT_AST.CharacterCharacter False -> pure $ VarE 'S.character `AppTypeE` LitT (NumTyLit 1) -- SQL CHARACTER -> Squeal character(1)
-      PGT_AST.CharacterCharacter True -> pure $ VarE 'S.text -- SQL CHARACTER VARYING -> Squeal text
-      PGT_AST.CharCharacter False -> pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit 1) -- SQL CHAR -> Squeal char(1)
-      PGT_AST.CharCharacter True -> pure $ VarE 'S.text -- SQL CHAR VARYING -> Squeal text
-      PGT_AST.VarcharCharacter -> pure $ VarE 'S.text -- SQL VARCHAR (no length) -> Squeal text
-      -- National character types are often aliases for standard character types in PostgreSQL
-      PGT_AST.NationalCharacterCharacter False -> pure $ VarE 'S.character `AppTypeE` LitT (NumTyLit 1) -- NCHAR -> character(1)
-      PGT_AST.NationalCharacterCharacter True -> pure $ VarE 'S.text -- NCHAR VARYING -> text
-      PGT_AST.NationalCharCharacter False -> pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit 1) -- NATIONAL CHAR -> char(1)
-      PGT_AST.NationalCharCharacter True -> pure $ VarE 'S.text -- NATIONAL CHAR VARYING -> text
-      PGT_AST.NcharCharacter False -> pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit 1) -- NCHAR (synonym for NATIONAL CHAR) -> char(1)
-      PGT_AST.NcharCharacter True -> pure $ VarE 'S.text -- NCHAR VARYING -> text
-  PGT_AST.ConstDatetimeSimpleTypename dt -> case dt of
-    PGT_AST.TimestampConstDatetime precision maybeTimezone -> do
-      when (isJust precision) $ fail "TIMESTAMP with precision is not supported."
-      pure $ case maybeTimezone of
-        Just False -> VarE 'S.timestampWithTimeZone -- WITH TIME ZONE
-        _ -> VarE 'S.timestamp -- WITHOUT TIME ZONE or unspecified
-    PGT_AST.TimeConstDatetime precision maybeTimezone -> do
-      when (isJust precision) $ fail "TIME with precision is not supported."
-      pure $ case maybeTimezone of
-        Just False -> VarE 'S.timeWithTimeZone -- WITH TIME ZONE
-        _ -> VarE 'S.time -- WITHOUT TIME ZONE or unspecified
-  PGT_AST.ConstIntervalSimpleTypename (Left (Just _)) ->
-    fail "INTERVAL with qualifiers is not supported."
-  PGT_AST.ConstIntervalSimpleTypename (Left Nothing) ->
-    pure $ VarE 'S.interval
-  PGT_AST.ConstIntervalSimpleTypename (Right _) ->
-    fail "INTERVAL with integer literal is not supported in this context."
-
-
-renderPGTNumeric :: PGT_AST.Numeric -> Q Exp
-renderPGTNumeric = \case
-  PGT_AST.IntNumeric -> pure $ VarE 'S.int
-  PGT_AST.IntegerNumeric -> pure $ VarE 'S.integer
-  PGT_AST.SmallintNumeric -> pure $ VarE 'S.smallint
-  PGT_AST.BigintNumeric -> pure $ VarE 'S.bigint
-  PGT_AST.RealNumeric -> pure $ VarE 'S.real
-  PGT_AST.FloatNumeric (Just _) -> fail "FLOAT with precision is not supported."
-  PGT_AST.FloatNumeric Nothing -> pure $ VarE 'S.float4
-  PGT_AST.DoublePrecisionNumeric -> pure $ VarE 'S.doublePrecision
-  PGT_AST.DecimalNumeric (Just _) -> fail "DECIMAL with precision/scale is not supported."
-  PGT_AST.DecimalNumeric Nothing -> pure $ VarE 'S.numeric
-  PGT_AST.DecNumeric (Just _) -> fail "DEC with precision/scale is not supported."
-  PGT_AST.DecNumeric Nothing -> pure $ VarE 'S.numeric
-  PGT_AST.NumericNumeric (Just _) -> fail "NUMERIC with precision/scale is not supported."
-  PGT_AST.NumericNumeric Nothing -> pure $ VarE 'S.numeric
-  PGT_AST.BooleanNumeric -> pure $ VarE 'S.bool
-
-
-{- |
-  Translates the `Targeting` clause of a SQL SELECT statement from the
-  `postgresql-syntax` AST (`PGT_AST.Targeting`) into a Squeal representation.
-  The `Targeting` clause defines the list of expressions or columns to be
-  returned by the query (e.g., `*`, `col1`, `col2 AS alias`, `DISTINCT col3`).
-
-  The function returns a Template Haskell `Q` computation that, when run,
-  produces a pair:
-  1. `Exp`: A Template Haskell expression representing the Squeal selection list.
-     This could be `S.Star` for `SELECT *`, or a constructed Squeal expression
-     for a list of target elements (e.g., `expression1 :* expression2 :* S.Nil`).
-  2. `Maybe [PGT_AST.AExpr]`: This field is used to pass along the expressions
-     from a `DISTINCT ON (expr1, expr2, ...)` clause. If the query uses
-     `DISTINCT ON`, this will be `Just` containing the list of `PGT_AST.AExpr`
-     nodes representing `expr1, expr2, ...`. For all other types of targeting
-     (e.g., `SELECT DISTINCT col`, `SELECT col1, col2`, `SELECT *`), this
-     will be `Nothing`.
-
-  The function handles different kinds of targeting:
-  - `PGT_AST.NormalTargeting`: Standard `SELECT col1, col2, ...`
-  - `PGT_AST.AllTargeting`: `SELECT ALL ...` (often equivalent to normal select or `SELECT *`)
-  - `PGT_AST.DistinctTargeting`: `SELECT DISTINCT ...` or `SELECT DISTINCT ON (...) ...`
-
-  Returns (SquealSelectionListExp, Maybe DistinctOnAstExpressions)
--}
-renderPGTTargeting
-  :: PGT_AST.Targeting
-  -> Q (Exp, Maybe [PGT_AST.AExpr])
-renderPGTTargeting = \case
-  PGT_AST.NormalTargeting targetList -> do
-    selListExp <- renderPGTTargetList targetList
-    pure (selListExp, Nothing)
-  PGT_AST.AllTargeting maybeTargetList -> do
-    selListExp <-
-      case maybeTargetList of
-        Nothing -> pure $ ConE 'S.Star -- SELECT ALL (which is like SELECT *)
-        Just tl -> renderPGTTargetList tl
-    pure (selListExp, Nothing)
-  PGT_AST.DistinctTargeting maybeOnExprs targetList -> do
-    selListExp <- renderPGTTargetList targetList
-    pure (selListExp, fmap NE.toList maybeOnExprs)
-
-
-renderPGTTargetEl :: PGT_AST.TargetEl -> Maybe PGT_AST.Ident -> Int -> Q Exp
-renderPGTTargetEl targetEl mOuterAlias idx =
-  let
-    (exprAST, mInternalAlias) = case targetEl of
-      PGT_AST.AliasedExprTargetEl e an -> (e, Just an)
-      PGT_AST.ImplicitlyAliasedExprTargetEl e an -> (e, Just an)
-      PGT_AST.ExprTargetEl e -> (e, Nothing)
-      PGT_AST.AsteriskTargetEl ->
-        ( PGT_AST.CExprAExpr
-            ( PGT_AST.AexprConstCExpr
-                PGT_AST.NullAexprConst
-            )
-        , Nothing -- Placeholder for Star, should be S.Star
-        )
-    finalAliasName = mOuterAlias <|> mInternalAlias
-  in
-    case targetEl of
-      PGT_AST.AsteriskTargetEl -> pure $ ConE 'S.Star
-      _ -> do
-        renderedScalarExp <- renderPGTAExpr exprAST
-        case exprAST of
-          PGT_AST.CExprAExpr (PGT_AST.ColumnrefCExpr _)
-            | Nothing <- finalAliasName ->
-                pure renderedScalarExp
-          _ -> do
-            let
-              aliasLabelStr =
-                case finalAliasName of
-                  Just ident -> Text.unpack $ getIdentText ident
-                  Nothing -> "_col" <> show idx
-            pure $
-              VarE 'S.as
-                `AppE` renderedScalarExp
-                `AppE` LabelE aliasLabelStr
-
-
-renderPGTTargetList :: PGT_AST.TargetList -> Q Exp
-renderPGTTargetList (item NE.:| items) =
-    if null items && isAsterisk item
-      then
-        pure $ ConE 'S.Star
-      else
-        if null items && isDotStar item
-          then
-            renderPGTTargetElDotStar item
-          else
-            go (item : items) 1
-  where
-    isAsterisk :: PGT_AST.TargetEl -> Bool
-    isAsterisk PGT_AST.AsteriskTargetEl = True
-    isAsterisk _ = False
-
-    isDotStar :: PGT_AST.TargetEl -> Bool
-    isDotStar
-      ( PGT_AST.ExprTargetEl
-          ( PGT_AST.CExprAExpr
-              (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref _ (Just indirection)))
-            )
-        ) =
-        any isAllIndirectionEl (NE.toList indirection)
-    isDotStar _ = False
-
-    isAllIndirectionEl :: PGT_AST.IndirectionEl -> Bool
-    isAllIndirectionEl PGT_AST.AllIndirectionEl = True
-    isAllIndirectionEl _ = False
-
-    renderPGTTargetElDotStar :: PGT_AST.TargetEl -> Q Exp
-    renderPGTTargetElDotStar
-      ( PGT_AST.ExprTargetEl
-          ( PGT_AST.CExprAExpr
-              ( PGT_AST.ColumnrefCExpr
-                  ( PGT_AST.Columnref
-                      qualName
-                      indirectionOpt
-                    )
-                )
-            )
-        ) =
-        case indirectionOpt of
-          Just indirection
-            | any isAllIndirectionEl (NE.toList indirection) ->
-                pure $
-                  ConE 'S.DotStar
-                    `AppE` (LabelE (Text.unpack (getIdentText qualName)))
-          _ ->
-            fail $
-              "renderPGTTargetElDotStar called with non-DotStar "
-                <> "TargetEl structure"
-    renderPGTTargetElDotStar _ =
-      fail "renderPGTTargetElDotStar called with unexpected TargetEl"
-
-    go :: [PGT_AST.TargetEl] -> Int -> Q Exp
-    go [] _ =
-      {- Should not happen with NonEmpty input to renderPGTTargetList -}
-      fail "Empty selection list items in go."
-    go [el] currentIdx = renderPGTTargetEl el Nothing currentIdx
-    go (el : more) currentIdx = do
-      renderedEl <- renderPGTTargetEl el Nothing currentIdx
-      if null more
-        then pure renderedEl
-        else do
-          restRendered <- go more (currentIdx + 1)
-          pure $ ConE 'S.Also `AppE` restRendered `AppE` renderedEl
-
-
diff --git a/src/Squeal/QuasiQuotes/Delete.hs b/src/Squeal/QuasiQuotes/Delete.hs
--- a/src/Squeal/QuasiQuotes/Delete.hs
+++ b/src/Squeal/QuasiQuotes/Delete.hs
@@ -9,13 +9,16 @@
 ) where
 
 import Control.Monad (when)
+import Data.Foldable (Foldable(foldr), foldlM)
 import Data.Maybe (isJust)
 import Language.Haskell.TH.Syntax (Exp(AppE, ConE, LabelE, VarE), Q)
 import Prelude
-  ( Applicative(pure), Maybe(Just, Nothing), MonadFail(fail), ($), (<$>)
+  ( Applicative(pure), Maybe(Just, Nothing), MonadFail(fail), Semigroup((<>))
+  , ($), (<$>)
   )
-import Squeal.QuasiQuotes.Common
+import Squeal.QuasiQuotes.Query
   ( getIdentText, renderPGTAExpr, renderPGTTableRef, renderPGTTargetList
+  , toSquealQuery
   )
 import qualified Data.List.NonEmpty as NE
 import qualified Data.Text as Text
@@ -23,6 +26,44 @@
 import qualified Squeal.PostgreSQL as S
 
 
+renderPGTWithClause :: PGT_AST.WithClause -> Q ([Text.Text], Exp)
+renderPGTWithClause (PGT_AST.WithClause recursive ctes) = do
+    when recursive $ fail "Recursive WITH clauses are not supported yet."
+    let
+      cteList = NE.toList ctes
+    (finalCteNames, renderedCtes) <-
+      foldlM
+        ( \(names, exps) cte -> do
+            (name, exp) <- renderCte names cte
+            pure (names <> [name], exps <> [exp])
+        )
+        ([], [])
+        cteList
+
+    let
+      withExp =
+        foldr
+          (\cte acc -> ConE '(S.:>>) `AppE` cte `AppE` acc)
+          (ConE 'S.Done)
+          renderedCtes
+    pure (finalCteNames, withExp)
+  where
+    renderCte :: [Text.Text] -> PGT_AST.CommonTableExpr -> Q (Text.Text, Exp)
+    renderCte existingCteNames (PGT_AST.CommonTableExpr ident maybeColNames maybeMaterialized stmt) = do
+      when (isJust maybeMaterialized) $
+        fail "MATERIALIZED/NOT MATERIALIZED for CTEs is not supported yet."
+      cteStmtExp <-
+        case stmt of
+          PGT_AST.SelectPreparableStmt selectStmt -> do
+            queryExp <- toSquealQuery existingCteNames maybeColNames selectStmt
+            pure $ VarE 'S.queryStatement `AppE` queryExp
+          _ -> fail "Only SELECT statements are supported in CTEs for now."
+      let
+        cteName = getIdentText ident
+      pure
+        (cteName, VarE 'S.as `AppE` cteStmtExp `AppE` LabelE (Text.unpack cteName))
+
+
 toSquealDelete :: PGT_AST.DeleteStmt -> Q Exp
 toSquealDelete
   ( PGT_AST.DeleteStmt
@@ -32,8 +73,12 @@
       maybeWhereClause
       maybeReturningClause
     ) = do
-    when (isJust maybeWithClause) $
-      fail "WITH clauses are not supported in DELETE statements yet."
+    (cteNames, renderedWithClause) <-
+      case maybeWithClause of
+        Nothing -> pure ([], Nothing)
+        Just withClause -> do
+          (names, exp) <- renderPGTWithClause withClause
+          pure (names, Just exp)
     -- whereOrCurrentClause with cursor is not supported yet
 
     targetTableExp <- renderPGTRelationExprOptAlias' relationExprOptAlias
@@ -41,28 +86,34 @@
     usingClauseExp <-
       case maybeUsingClause of
         Nothing -> pure $ ConE 'S.NoUsing
-        Just usingClause -> AppE (ConE 'S.Using) <$> renderPGTTableRef usingClause
+        Just usingClause -> AppE (ConE 'S.Using) <$> renderPGTTableRef cteNames usingClause
 
     whereConditionExp <-
       case maybeWhereClause of
         Nothing ->
           fail
             "DELETE statements must have a WHERE clause for safety. Use WHERE TRUE to delete all rows."
-        Just (PGT_AST.ExprWhereOrCurrentClause whereAExpr) -> renderPGTAExpr whereAExpr
+        Just (PGT_AST.ExprWhereOrCurrentClause whereAExpr) -> renderPGTAExpr cteNames whereAExpr
         Just (PGT_AST.CursorWhereOrCurrentClause _) -> fail "WHERE CURRENT OF is not supported."
 
     returningClauseExp <-
       case maybeReturningClause of
         Nothing -> pure $ ConE 'S.Returning_ `AppE` ConE 'S.Nil
-        Just returningClause -> AppE (ConE 'S.Returning) <$> renderPGTTargetList returningClause
+        Just returningClause -> AppE (ConE 'S.Returning) <$> renderPGTTargetList cteNames returningClause
 
-    pure
-      ( VarE 'S.deleteFrom
-          `AppE` targetTableExp
-          `AppE` usingClauseExp
-          `AppE` whereConditionExp
-          `AppE` returningClauseExp
-      )
+    let
+      deleteBody =
+        ( VarE 'S.deleteFrom
+            `AppE` targetTableExp
+            `AppE` usingClauseExp
+            `AppE` whereConditionExp
+            `AppE` returningClauseExp
+        )
+    let
+      finalExp = case renderedWithClause of
+        Nothing -> deleteBody
+        Just withExp -> VarE 'S.with `AppE` withExp `AppE` deleteBody
+    pure finalExp
 
 
 renderPGTRelationExprOptAlias' :: PGT_AST.RelationExprOptAlias -> Q Exp
diff --git a/src/Squeal/QuasiQuotes/Insert.hs b/src/Squeal/QuasiQuotes/Insert.hs
--- a/src/Squeal/QuasiQuotes/Insert.hs
+++ b/src/Squeal/QuasiQuotes/Insert.hs
@@ -9,21 +9,58 @@
 ) where
 
 import Control.Monad (MonadFail(fail), mapM, when, zipWithM)
+import Data.Foldable (Foldable(foldr, length), foldlM)
 import Data.Maybe (isJust)
 import Language.Haskell.TH.Syntax (Exp(AppE, ConE, LabelE, ListE, VarE), Q)
 import Prelude
-  ( Applicative(pure), Either(Left), Eq((/=)), Foldable(foldr, length)
-  , Maybe(Just, Nothing), Semigroup((<>)), Show(show), ($), (.), error
-  , otherwise
+  ( Applicative(pure), Either(Left), Eq((/=)), Maybe(Just, Nothing)
+  , Semigroup((<>)), Show(show), ($), (.), error, otherwise
   )
-import Squeal.QuasiQuotes.Common (getIdentText, renderPGTAExpr)
-import Squeal.QuasiQuotes.Query (toSquealQuery)
+import Squeal.QuasiQuotes.Query (getIdentText, renderPGTAExpr, toSquealQuery)
 import qualified Data.List.NonEmpty as NE
 import qualified Data.Text as Text
 import qualified PostgresqlSyntax.Ast as PGT_AST
 import qualified Squeal.PostgreSQL as S
 
 
+renderPGTWithClause :: PGT_AST.WithClause -> Q ([Text.Text], Exp)
+renderPGTWithClause (PGT_AST.WithClause recursive ctes) = do
+    when recursive $ fail "Recursive WITH clauses are not supported yet."
+    let
+      cteList = NE.toList ctes
+    (finalCteNames, renderedCtes) <-
+      foldlM
+        ( \(names, exps) cte -> do
+            (name, exp) <- renderCte names cte
+            pure (names <> [name], exps <> [exp])
+        )
+        ([], [])
+        cteList
+
+    let
+      withExp =
+        foldr
+          (\cte acc -> ConE '(S.:>>) `AppE` cte `AppE` acc)
+          (ConE 'S.Done)
+          renderedCtes
+    pure (finalCteNames, withExp)
+  where
+    renderCte :: [Text.Text] -> PGT_AST.CommonTableExpr -> Q (Text.Text, Exp)
+    renderCte existingCteNames (PGT_AST.CommonTableExpr ident maybeColNames maybeMaterialized stmt) = do
+      when (isJust maybeMaterialized) $
+        fail "MATERIALIZED/NOT MATERIALIZED for CTEs is not supported yet."
+      cteStmtExp <-
+        case stmt of
+          PGT_AST.SelectPreparableStmt selectStmt -> do
+            queryExp <- toSquealQuery existingCteNames maybeColNames selectStmt
+            pure $ VarE 'S.queryStatement `AppE` queryExp
+          _ -> fail "Only SELECT statements are supported in CTEs for now."
+      let
+        cteName = getIdentText ident
+      pure
+        (cteName, VarE 'S.as `AppE` cteStmtExp `AppE` LabelE (Text.unpack cteName))
+
+
 toSquealInsert :: PGT_AST.InsertStmt -> Q Exp
 toSquealInsert
   ( PGT_AST.InsertStmt
@@ -33,8 +70,13 @@
       maybeOnConflict
       maybeReturningClause
     ) = do
-    when (isJust maybeWithClause) $
-      fail "WITH clauses are not supported in INSERT statements yet."
+    (cteNames, renderedWithClause) <-
+      case maybeWithClause of
+        Nothing -> pure ([], Nothing)
+        Just withClause -> do
+          (names, exp) <- renderPGTWithClause withClause
+          pure (names, Just exp)
+
     when (isJust maybeOnConflict) $
       fail "ON CONFLICT clauses are not supported yet."
 
@@ -50,7 +92,7 @@
                 (PGT_AST.SelectNoParens _ (Left (PGT_AST.ValuesSimpleSelect valuesClause)) _ _ _) ->
                   case maybeInsertColumnList of
                     Just colItems ->
-                      renderPGTValueRows (NE.toList colItems) valuesClause
+                      renderPGTValueRows cteNames (NE.toList colItems) valuesClause
                     Nothing ->
                       fail
                         "INSERT INTO ... VALUES must specify column names for the Squeal-QQ translation."
@@ -62,7 +104,7 @@
                     fail
                       "INSERT INTO table (columns) SELECT ... is not yet supported by Squeal-QQ. Please use INSERT INTO table SELECT ... and ensure your SELECT statement provides all columns for the table, matching the table's column order and types."
                   Nothing -> do
-                    squealQueryExp <- toSquealQuery selectStmt -- from Squeal.QuasiQuotes.Query
+                    squealQueryExp <- toSquealQuery cteNames Nothing selectStmt -- from Squeal.QuasiQuotes.Query
                     pure (ConE 'S.Subquery `AppE` squealQueryExp)
           let
             table = renderPGTInsertTarget insertTarget
@@ -79,7 +121,7 @@
                   `AppE` ConE 'S.OnConflictDoRaise
                   `AppE` returning
             Just targetList -> do
-              returningProj <- renderTargetList (NE.toList targetList)
+              returningProj <- renderTargetList cteNames (NE.toList targetList)
               let
                 returning = ConE 'S.Returning `AppE` (ConE 'S.List `AppE` returningProj)
               pure $
@@ -91,12 +133,16 @@
         PGT_AST.DefaultValuesInsertRest ->
           fail "INSERT INTO ... DEFAULT VALUES is not yet supported by Squeal-QQ."
 
-    pure insertBody
+    let
+      finalExp = case renderedWithClause of
+        Nothing -> insertBody
+        Just withExp -> VarE 'S.with `AppE` withExp `AppE` insertBody
+    pure finalExp
 
 
-renderTargetList :: [PGT_AST.TargetEl] -> Q Exp
-renderTargetList targetEls = do
-  exps <- mapM renderTargetEl targetEls
+renderTargetList :: [Text.Text] -> [PGT_AST.TargetEl] -> Q Exp
+renderTargetList cteNames targetEls = do
+  exps <- mapM (renderTargetEl cteNames) targetEls
   pure $
     foldr
       (\h t -> ConE '(S.:*) `AppE` h `AppE` t)
@@ -104,10 +150,10 @@
       exps
 
 
-renderTargetEl :: PGT_AST.TargetEl -> Q Exp
-renderTargetEl = \case
+renderTargetEl :: [Text.Text] -> PGT_AST.TargetEl -> Q Exp
+renderTargetEl cteNames = \case
   PGT_AST.ExprTargetEl expr -> do
-    exprExp <- renderPGTAExpr expr
+    exprExp <- renderPGTAExpr cteNames expr
     case expr of
       PGT_AST.CExprAExpr (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing)) ->
         let
@@ -118,7 +164,7 @@
         fail
           "Returning expression without an alias is not supported for this expression type. Please add an alias."
   PGT_AST.AliasedExprTargetEl expr alias -> do
-    exprExp <- renderPGTAExpr expr
+    exprExp <- renderPGTAExpr cteNames expr
     pure $
       VarE 'S.as `AppE` exprExp `AppE` LabelE (Text.unpack (getIdentText alias))
   PGT_AST.AsteriskTargetEl -> fail "should be handled by toSquealInsert"
@@ -161,49 +207,49 @@
 
 
 renderPGTValueRows
-  :: [PGT_AST.InsertColumnItem] -> PGT_AST.ValuesClause -> Q Exp
-renderPGTValueRows colItems (valuesClauseRows) =
+  :: [Text.Text] -> [PGT_AST.InsertColumnItem] -> PGT_AST.ValuesClause -> Q Exp
+renderPGTValueRows cteNames colItems (valuesClauseRows) =
   -- valuesClauseRows is NonEmpty (NonEmpty AExpr)
   case NE.toList valuesClauseRows of
     [] -> fail "Insert statement has no value rows."
     row : moreRows -> do
-      firstRowExp <- renderPGTValueRow colItems (NE.toList row)
-      moreRowsExp <- mapM (renderPGTValueRow colItems . NE.toList) moreRows
+      firstRowExp <- renderPGTValueRow cteNames colItems (NE.toList row)
+      moreRowsExp <- mapM (renderPGTValueRow cteNames colItems . NE.toList) moreRows
       pure $
         ConE 'S.Values
           `AppE` firstRowExp
           `AppE` ListE moreRowsExp
 
 
-renderPGTValueRow :: [PGT_AST.InsertColumnItem] -> [PGT_AST.AExpr] -> Q Exp
-renderPGTValueRow colItems exprs
-    | length colItems /= length exprs =
-        fail "Mismatched number of column names and values in INSERT statement."
-    | otherwise = do
-        processedItems <- zipWithM processItem colItems exprs
-        pure $ foldr nvpToCons (ConE 'S.Nil) processedItems
-  where
-    nvpToCons :: Exp -> Exp -> Exp
-    nvpToCons item acc = ConE '(S.:*) `AppE` item `AppE` acc
+renderPGTValueRow :: [Text.Text] -> [PGT_AST.InsertColumnItem] -> [PGT_AST.AExpr] -> Q Exp
+renderPGTValueRow cteNames colItems exprs
+  | length colItems /= length exprs =
+      fail "Mismatched number of column names and values in INSERT statement."
+  | otherwise = do
+      processedItems <- zipWithM processItem colItems exprs
+      pure $ foldr nvpToCons (ConE 'S.Nil) processedItems
+ where
+  nvpToCons :: Exp -> Exp -> Exp
+  nvpToCons item acc = ConE '(S.:*) `AppE` item `AppE` acc
 
-    processItem :: PGT_AST.InsertColumnItem -> PGT_AST.AExpr -> Q Exp
-    processItem (PGT_AST.InsertColumnItem colId maybeIndirection) expr = do
-      when (isJust maybeIndirection) $
-        fail "INSERT with indirection (e.g., array access) is not supported."
-      let
-        colNameStr = Text.unpack (getIdentText colId)
-      case expr of
-        PGT_AST.DefaultAExpr ->
-          -- Check for DEFAULT keyword
-          pure $
-            VarE 'S.as
-              `AppE` ConE 'S.Default
-              `AppE` LabelE colNameStr
-        _ -> do
-          renderedExpr <- renderPGTAExpr expr
-          pure $
-            VarE 'S.as
-              `AppE` (ConE 'S.Set `AppE` renderedExpr)
-              `AppE` LabelE colNameStr
+  processItem :: PGT_AST.InsertColumnItem -> PGT_AST.AExpr -> Q Exp
+  processItem (PGT_AST.InsertColumnItem colId maybeIndirection) expr = do
+    when (isJust maybeIndirection) $
+      fail "INSERT with indirection (e.g., array access) is not supported."
+    let
+      colNameStr = Text.unpack (getIdentText colId)
+    case expr of
+      PGT_AST.DefaultAExpr ->
+        -- Check for DEFAULT keyword
+        pure $
+          VarE 'S.as
+            `AppE` ConE 'S.Default
+            `AppE` LabelE colNameStr
+      _ -> do
+        renderedExpr <- renderPGTAExpr cteNames expr
+        pure $
+          VarE 'S.as
+            `AppE` (ConE 'S.Set `AppE` renderedExpr)
+            `AppE` LabelE colNameStr
 
 
diff --git a/src/Squeal/QuasiQuotes/Query.hs b/src/Squeal/QuasiQuotes/Query.hs
--- a/src/Squeal/QuasiQuotes/Query.hs
+++ b/src/Squeal/QuasiQuotes/Query.hs
@@ -1,568 +1,1603 @@
-{-# LANGUAGE ExplicitForAll #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskellQuotes #-}
-{-# LANGUAGE ViewPatterns #-}
-
--- | Description: Translate query expressions.
-module Squeal.QuasiQuotes.Query (
-  toSquealQuery,
-) where
-
-import Control.Monad (unless, when)
-import Data.Foldable (Foldable(foldl', foldr, null))
-import Data.Maybe (fromMaybe, isJust, isNothing)
-import Language.Haskell.TH.Syntax
-  ( Exp(AppE, ConE, InfixE, LabelE, ListE, LitE, VarE), Lit(IntegerL), Q, mkName
-  )
-import Prelude
-  ( Applicative(pure), Bool(False, True), Either(Left, Right)
-  , Maybe(Just, Nothing), MonadFail(fail), Num((+)), Ord((>=)), Semigroup((<>))
-  , Show(show), Traversable(mapM), ($), (&&), (<$>), Int, fromIntegral, zip
-  )
-import Squeal.QuasiQuotes.Common
-  ( getIdentText, renderPGTAExpr, renderPGTTableRef, renderPGTTargeting
-  )
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Text as Text
-import qualified PostgresqlSyntax.Ast as PGT_AST
-import qualified Squeal.PostgreSQL as S
-
-
-toSquealQuery :: PGT_AST.SelectStmt -> Q Exp
-toSquealQuery selectStmt = case selectStmt of
-  Left selectNoParens -> toSquealSelectNoParens selectNoParens
-  Right selectWithParens -> toSquealSelectWithParens selectWithParens
-
-
-toSquealSelectWithParens :: PGT_AST.SelectWithParens -> Q Exp
-toSquealSelectWithParens = \case
-  PGT_AST.NoParensSelectWithParens snp -> toSquealSelectNoParens snp
-  PGT_AST.WithParensSelectWithParens swp ->
-    {- The AST structure itself should handle precedence.  Just recurse.  -}
-    toSquealSelectWithParens swp
-
-
-toSquealSelectNoParens :: PGT_AST.SelectNoParens -> Q Exp
-toSquealSelectNoParens
-  ( PGT_AST.SelectNoParens
-      maybeWithClause
-      selectClause
-      maybeSortClause
-      maybeSelectLimit
-      maybeForLockingClause
-    ) = do
-    when (isJust maybeWithClause) $ fail "WITH clauses are not supported yet."
-    case selectClause of
-      Left simpleSelect ->
-        toSquealSimpleSelect
-          simpleSelect
-          maybeSortClause
-          maybeSelectLimit
-          maybeForLockingClause
-      Right selectWithParens' -> toSquealSelectWithParens selectWithParens'
-
-
-toSquealSimpleSelect
-  :: PGT_AST.SimpleSelect
-  -> Maybe PGT_AST.SortClause
-  -> Maybe PGT_AST.SelectLimit
-  -> Maybe PGT_AST.ForLockingClause
-  -> Q Exp
-toSquealSimpleSelect simpleSelect maybeSortClause maybeSelectLimit maybeForLockingClause =
-  case simpleSelect of
-    PGT_AST.ValuesSimpleSelect valuesClause -> do
-      unless
-        ( isNothing maybeSortClause
-            && isNothing maybeSelectLimit
-            && isNothing maybeForLockingClause
-        )
-        $ fail
-        $ "ORDER BY / OFFSET / LIMIT / FOR UPDATE etc. not supported with VALUES clause "
-          <> "in this translation yet."
-      renderedValues <- renderValuesClauseToNP valuesClause
-      pure $ VarE 'S.values_ `AppE` renderedValues
-    PGT_AST.NormalSimpleSelect
-      maybeTargeting
-      maybeIntoClause
-      maybeFromClause
-      maybeWhereClause
-      maybeGroupClause
-      maybeHavingClause
-      maybeWindowClause ->
-        do
-          targeting <-
-            case maybeTargeting of
-              Nothing ->
-                fail "SELECT without a selection list is not supported."
-              Just targeting -> pure targeting
-          case
-              ( maybeFromClause
-              , maybeGroupClause
-              , maybeHavingClause
-              , maybeIntoClause
-              , maybeSelectLimit
-              , maybeWhereClause
-              , maybeWindowClause
-              )
-            of
-              ( Nothing
-                , Nothing
-                , Nothing
-                , Nothing
-                , Nothing
-                , Nothing
-                , Nothing
-                ) ->
-                  do
-                    -- Case: SELECT <targeting_list> (no FROM, no other clauses)
-                    -- Translate to S.values_
-                    renderedTargetingForValues <-
-                      renderPGTTargetingForValues targeting
-                    pure $
-                      VarE 'S.values_ `AppE` renderedTargetingForValues
-              (Nothing, _, _, _, _, _, _) ->
-                {-
-                  Case: SELECT <targeting_list> (no FROM, but other
-                  clauses are present)
-                -}
-                fail $
-                  "SELECT with targeting but no FROM clause cannot have "
-                    <> "other clauses like INTO, WHERE, GROUP BY, HAVING, "
-                    <> "WINDOW, or LIMIT/OFFSET."
-              (Just fromClause, _, _, _, _, _, _) -> do
-                -- Case: SELECT ... FROM ... (original logic)
-
-                when (isJust maybeIntoClause) $
-                  fail "INTO clause is not yet supported in this translation."
-                when (isJust maybeWindowClause) $
-                  fail $
-                    "WINDOW clause is not yet supported in this translation "
-                      <> "for NormalSimpleSelect with FROM."
-
-                renderedFromClauseExp <- renderPGTTableRef fromClause
-                let
-                  baseTableExpr = VarE 'S.from `AppE` renderedFromClauseExp
-
-                tableExprWithWhere <-
-                  case maybeWhereClause of
-                    Nothing -> pure baseTableExpr
-                    Just wc -> do
-                      renderedWC <- renderPGTAExpr wc
-                      pure $
-                        InfixE
-                          (Just baseTableExpr)
-                          (VarE '(S.&))
-                          (Just (AppE (VarE 'S.where_) renderedWC))
-
-                tableExprWithGroupBy <-
-                  applyPGTGroupBy tableExprWithWhere maybeGroupClause
-
-                tableExprWithHaving <-
-                  case maybeHavingClause of
-                    Nothing -> pure tableExprWithGroupBy
-                    Just hc -> do
-                      when (isNothing maybeGroupClause) $
-                        fail "HAVING clause requires a GROUP BY clause."
-                      renderedHC <- renderPGTAExpr hc
-                      pure $
-                        InfixE
-                          (Just tableExprWithGroupBy)
-                          (VarE '(S.&))
-                          (Just (AppE (VarE 'S.having) renderedHC))
-
-                tableExprWithOrderBy <-
-                  case maybeSortClause of
-                    Nothing -> pure tableExprWithHaving
-                    Just sortClause -> do
-                      renderedSC <- renderPGTSortClause sortClause
-                      pure $
-                        InfixE
-                          (Just tableExprWithHaving)
-                          (VarE '(S.&))
-                          (Just (AppE (VarE 'S.orderBy) renderedSC))
-
-                (tableExprWithOffset, mTableExprWithLimit) <-
-                  processSelectLimit tableExprWithOrderBy maybeSelectLimit
-
-                let
-                  baseFinalTableExpr =
-                    fromMaybe tableExprWithOffset mTableExprWithLimit
-
-                -- Apply FOR LOCKING clause if present
-                finalTableExprWithPotentialLocking <-
-                  case maybeForLockingClause of
-                    Nothing -> pure baseFinalTableExpr
-                    Just flc -> do
-                      lockingClauseExps <- renderPGTForLockingClauseItems flc
-                      pure $
-                        foldl'
-                          ( \accTableExpr lockingClauseExp ->
-                              InfixE
-                                (Just accTableExpr)
-                                (VarE '(S.&))
-                                (Just (AppE (VarE 'S.lockRows) lockingClauseExp))
-                          )
-                          baseFinalTableExpr
-                          lockingClauseExps
-
-                (selectionTargetExp, maybeDistinctOnExprs) <-
-                  renderPGTTargeting targeting
-
-                squealSelectFn <-
-                  case maybeDistinctOnExprs of
-                    Nothing ->
-                      case targeting of
-                        PGT_AST.DistinctTargeting Nothing _ ->
-                          pure $ VarE 'S.selectDistinct
-                        _ -> pure $ VarE 'S.select -- Normal or ALL
-                    Just distinctOnAstExprs -> do
-                      distinctOnSquealSortExps <-
-                        renderPGTOnExpressionsClause distinctOnAstExprs
-                      pure $
-                        VarE 'S.selectDistinctOn
-                          `AppE` distinctOnSquealSortExps
-
-                pure $
-                  squealSelectFn
-                    `AppE` selectionTargetExp
-                    `AppE` finalTableExprWithPotentialLocking
-    unsupportedSimpleSelect ->
-      fail $
-        "Unsupported simple select statement: "
-          <> show unsupportedSimpleSelect
-
-
--- Helper for VALUES clause: Assumes S.values_ for a single row of values.
--- PGT_AST.ValuesClause is NonEmpty (NonEmpty PGT_AST.AExpr)
-renderValuesClauseToNP :: PGT_AST.ValuesClause -> Q Exp
-renderValuesClauseToNP (firstRowExps NE.:| restRowExps) = do
-    unless (null restRowExps) $
-      fail $
-        "Multi-row VALUES clause requires S.values, this translation "
-          <> "currently supports single row S.values_."
-    convertRowToNP firstRowExps
-  where
-    convertRowToNP :: NE.NonEmpty PGT_AST.AExpr -> Q Exp
-    convertRowToNP exprs =
-        go (NE.toList exprs) 1
-      where
-        go :: [PGT_AST.AExpr] -> Int -> Q Exp
-        go [] _ = pure $ ConE 'S.Nil
-        go (expr : fs) idx = do
-          renderedExpr <- renderPGTAExpr expr
-          let
-            aliasText = "_column" <> show idx -- Default alias for VALUES
-            aliasedExp = VarE 'S.as `AppE` renderedExpr `AppE` LabelE aliasText
-          restExp <- go fs (idx + 1) -- restExp is Exp here
-          -- Correct construction: aliasedExp :* restExp
-          pure $ ConE '(S.:*) `AppE` aliasedExp `AppE` restExp
-
-
-renderPGTForLockingClauseItems :: PGT_AST.ForLockingClause -> Q [Exp]
-renderPGTForLockingClauseItems = \case
-  PGT_AST.ReadOnlyForLockingClause ->
-    fail $
-      "FOR READ ONLY is not supported as a row-level locking "
-        <> "clause by Squeal-QQ."
-  PGT_AST.ItemsForLockingClause itemsNe ->
-    mapM renderPGTForLockingItem (NE.toList itemsNe)
-
-
-renderPGTForLockingItem :: PGT_AST.ForLockingItem -> Q Exp
-renderPGTForLockingItem
-  ( PGT_AST.ForLockingItem
-      strength
-      maybeTables
-      waitingOpt
-    ) = do
-    squealStrength <- renderPGTForLockingStrength strength
-    squealTables <-
-      case maybeTables of
-        Nothing ->
-          {- Empty list for "OF" tables means all tables in query -}
-          pure $ ConE 'S.Nil
-        Just tablesNe -> do
-          aliasExps <-
-            mapM
-              ( \qn -> case qn of
-                  PGT_AST.SimpleQualifiedName ident ->
-                    pure $ LabelE (Text.unpack $ getIdentText ident)
-                  _ ->
-                    fail $
-                      "Qualified table names like schema.table in "
-                        <> "FOR UPDATE/SHARE OF clauses are not yet "
-                        <> "supported. Please use simple table aliases "
-                        <> "that refer to tables in the FROM clause."
-              )
-              (NE.toList tablesNe)
-          pure $
-            foldr
-              (\itemExp acc -> ConE '(S.:*) `AppE` itemExp `AppE` acc)
-              (ConE 'S.Nil)
-              aliasExps
-
-    squealWaiting <- renderPGTWaiting waitingOpt
-
-    pure $
-      ConE 'S.For `AppE` squealStrength `AppE` squealTables `AppE` squealWaiting
-
-
-renderPGTForLockingStrength :: PGT_AST.ForLockingStrength -> Q Exp
-renderPGTForLockingStrength = \case
-  PGT_AST.UpdateForLockingStrength -> pure $ ConE 'S.Update
-  PGT_AST.NoKeyUpdateForLockingStrength -> pure $ ConE 'S.NoKeyUpdate
-  PGT_AST.ShareForLockingStrength -> pure $ ConE 'S.Share
-  PGT_AST.KeyForLockingStrength -> pure $ ConE 'S.KeyShare
-
-
-renderPGTWaiting :: Maybe Bool -> Q Exp
-renderPGTWaiting = \case
-  Nothing -> pure $ ConE 'S.Wait -- Default (no NOWAIT or SKIP LOCKED)
-  Just False -> pure $ ConE 'S.NoWait -- NOWAIT
-  Just True -> pure $ ConE 'S.SkipLocked -- SKIP LOCKED
-
-
-applyPGTGroupBy :: Exp -> Maybe PGT_AST.GroupClause -> Q Exp
-applyPGTGroupBy currentTableExpr = \case
-    Nothing -> pure currentTableExpr
-    Just groupClause -> do
-      renderedGB <- renderPGTGroupByClauseElements groupClause
-      pure $
-        InfixE
-          (Just currentTableExpr)
-          (VarE '(S.&))
-          (Just (AppE (VarE 'S.groupBy) renderedGB))
-  where
-    renderPGTGroupByClauseElements :: PGT_AST.GroupClause -> Q Exp
-    renderPGTGroupByClauseElements = \case
-      PGT_AST.EmptyGroupingSetGroupByItem NE.:| [] ->
-        pure $ ConE 'S.Nil
-      groupByItems -> do
-        renderedExprs <- mapM renderPGTGroupByItem (NE.toList groupByItems)
-        pure $
-          foldr
-            (\expr acc -> ConE '(S.:*) `AppE` expr `AppE` acc)
-            (ConE 'S.Nil)
-            renderedExprs
-
-
-renderPGTGroupByItem :: PGT_AST.GroupByItem -> Q Exp
-renderPGTGroupByItem = \case
-  PGT_AST.ExprGroupByItem scalarExpr -> renderPGTAExpr scalarExpr
-  PGT_AST.EmptyGroupingSetGroupByItem -> pure (ConE 'S.Nil)
-  unsupportedGroup ->
-    fail $
-      "Unsupported grouping expression: " <> show unsupportedGroup
-
-
-processSelectLimit :: Exp -> Maybe PGT_AST.SelectLimit -> Q (Exp, Maybe Exp)
-processSelectLimit tableExpr Nothing = pure (tableExpr, Nothing)
-processSelectLimit tableExpr (Just selectLimit) = do
-  let
-    (maybeOffsetClause, maybeLimitClause) = case selectLimit of
-      PGT_AST.LimitOffsetSelectLimit lim off -> (Just off, Just lim)
-      PGT_AST.OffsetLimitSelectLimit off lim -> (Just off, Just lim)
-      PGT_AST.LimitSelectLimit lim -> (Nothing, Just lim)
-      PGT_AST.OffsetSelectLimit off -> (Just off, Nothing)
-
-  tableExprWithOffset <-
-    case maybeOffsetClause of
-      Nothing -> pure tableExpr
-      Just offsetVal -> do
-        offsetExp <- renderPGTOffsetClause offsetVal
-        pure $
-          InfixE
-            (Just tableExpr)
-            (VarE '(S.&))
-            (Just (AppE (VarE 'S.offset) offsetExp))
-
-  case maybeLimitClause of
-    Nothing -> pure (tableExprWithOffset, Nothing)
-    Just limitVal -> do
-      limitExp <- renderPGTLimitClause limitVal
-      pure
-        ( tableExprWithOffset
-        , Just
-            ( InfixE
-                (Just tableExprWithOffset)
-                (VarE '(S.&))
-                (Just (AppE (VarE 'S.limit) limitExp))
-            )
-        )
-
-
-renderPGTLimitClause :: PGT_AST.LimitClause -> Q Exp
-renderPGTLimitClause = \case
-  PGT_AST.LimitLimitClause slValue mOffsetVal -> do
-    when (isJust mOffsetVal) $
-      fail
-        "LIMIT with comma (e.g. LIMIT x, y) is not supported. Use separate LIMIT and OFFSET clauses."
-    case slValue of
-      PGT_AST.ExprSelectLimitValue
-        ( PGT_AST.CExprAExpr
-            ( PGT_AST.FuncCExpr
-                ( PGT_AST.ApplicationFuncExpr
-                    ( PGT_AST.FuncApplication
-                        (PGT_AST.TypeFuncName (PGT_AST.UnquotedIdent "inline"))
-                        ( Just
-                            ( PGT_AST.NormalFuncApplicationParams
-                                Nothing
-                                ( PGT_AST.ExprFuncArgExpr
-                                    ( PGT_AST.CExprAExpr
-                                        (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing))
-                                      )
-                                    NE.:| []
-                                  )
-                                Nothing
-                              )
-                          )
-                      )
-                    Nothing
-                    Nothing
-                    Nothing
-                  )
-              )
-          ) -> pure $ VarE (mkName (Text.unpack (getIdentText ident)))
-      PGT_AST.ExprSelectLimitValue
-        (PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) ->
-          if n >= 0
-            then pure (LitE (IntegerL (fromIntegral n)))
-            else fail $ "LIMIT value must be non-negative: " <> show n
-      PGT_AST.AllSelectLimitValue ->
-        fail "LIMIT ALL not supported in this translation."
-      expr -> fail $ "Unsupported LIMIT expression: " <> show expr
-  PGT_AST.FetchOnlyLimitClause{} ->
-    fail "FETCH clause is not fully supported yet."
-
-
-renderPGTOffsetClause :: PGT_AST.OffsetClause -> Q Exp
-renderPGTOffsetClause = \case
-  PGT_AST.ExprOffsetClause
-    ( PGT_AST.CExprAExpr
-        ( PGT_AST.FuncCExpr
-            ( PGT_AST.ApplicationFuncExpr
-                ( PGT_AST.FuncApplication
-                    (PGT_AST.TypeFuncName (PGT_AST.UnquotedIdent "inline"))
-                    ( Just
-                        ( PGT_AST.NormalFuncApplicationParams
-                            Nothing
-                            ( PGT_AST.ExprFuncArgExpr
-                                ( PGT_AST.CExprAExpr
-                                    (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing))
-                                  )
-                                NE.:| []
-                              )
-                            Nothing
-                          )
-                      )
-                  )
-                Nothing
-                Nothing
-                Nothing
-              )
-          )
-      ) -> pure $ VarE (mkName (Text.unpack (getIdentText ident)))
-  PGT_AST.ExprOffsetClause
-    (PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) ->
-      if n >= 0
-        then pure (LitE (IntegerL (fromIntegral n)))
-        else fail $ "OFFSET value must be non-negative: " <> show n
-  PGT_AST.ExprOffsetClause expr ->
-    fail $
-      "Unsupported OFFSET expression: " <> show expr
-  PGT_AST.FetchFirstOffsetClause{} ->
-    fail "OFFSET with FETCH FIRST clause is not supported yet."
-
-
--- Helper to render a single TargetEl for S.values_
--- Each expression must be aliased.
-renderPGTTargetElForValues :: PGT_AST.TargetEl -> Int -> Q Exp
-renderPGTTargetElForValues targetEl idx = do
-  (exprAST, mUserAlias) <-
-    case targetEl of
-      PGT_AST.AliasedExprTargetEl e an -> pure (e, Just an)
-      PGT_AST.ImplicitlyAliasedExprTargetEl e an -> pure (e, Just an)
-      PGT_AST.ExprTargetEl e -> pure (e, Nothing)
-      PGT_AST.AsteriskTargetEl ->
-        fail "SELECT * is not supported unless there is a from clause."
-  renderedScalarExp <- renderPGTAExpr exprAST
-  let
-    aliasLabelStr =
-      case mUserAlias of
-        Just ident -> Text.unpack $ getIdentText ident
-        Nothing -> "_col" <> show idx -- Default alias for VALUES items
-  pure $ VarE 'S.as `AppE` renderedScalarExp `AppE` LabelE aliasLabelStr
-
-
--- Helper to render a TargetList into an NP list for S.values_
-renderPGTTargetListForValues :: PGT_AST.TargetList -> Q Exp
-renderPGTTargetListForValues (item NE.:| items) = do
-  renderedItems <-
-    mapM
-      (\(el, idx) -> renderPGTTargetElForValues el idx)
-      (zip (item : items) [1 ..])
-  -- Construct NP list: e1 :* e2 :* ... :* Nil
-  -- Each element in renderedItems is an Exp.
-  -- The result of the fold should be an Exp.
-  -- Then pure the final Exp.
-  pure $
-    foldr
-      (\hd acc -> ConE '(S.:*) `AppE` hd `AppE` acc)
-      (ConE 'S.Nil)
-      renderedItems
-
-
--- New function to render Targeting specifically for S.values_
-renderPGTTargetingForValues :: PGT_AST.Targeting -> Q Exp
-renderPGTTargetingForValues = \case
-  PGT_AST.NormalTargeting targetList -> renderPGTTargetListForValues targetList
-  PGT_AST.AllTargeting (Just targetList) ->
-    renderPGTTargetListForValues targetList
-  PGT_AST.AllTargeting Nothing ->
-    fail $
-      "SELECT * (ALL targeting without a list) is not supported "
-        <> "with VALUES clause."
-  PGT_AST.DistinctTargeting{} ->
-    -- Handles both DISTINCT and DISTINCT ON
-    fail $
-      "DISTINCT and DISTINCT ON queries are not supported with VALUES clause in "
-        <> "this translation."
-
-
-renderPGTOnExpressionsClause :: [PGT_AST.AExpr] -> Q Exp
-renderPGTOnExpressionsClause exprs = do
-    renderedSortExps <- mapM renderToSortExpr exprs
-    pure $ ListE renderedSortExps
-  where
-    renderToSortExpr :: PGT_AST.AExpr -> Q Exp
-    renderToSortExpr astExpr = do
-      squealExpr <- renderPGTAExpr astExpr
-      -- For DISTINCT ON, the direction (ASC/DESC) and NULLS order
-      -- are typically specified in the ORDER BY clause.
-      -- Here, we default to ASC for the SortExpression constructor.
-      pure $ ConE 'S.Asc `AppE` squealExpr
-
-
-renderPGTSortClause :: PGT_AST.SortClause -> Q Exp
-renderPGTSortClause sortBys = ListE <$> mapM renderPGTSortBy (NE.toList sortBys)
-
-
-renderPGTSortBy :: PGT_AST.SortBy -> Q Exp
-renderPGTSortBy = \case
-  PGT_AST.AscDescSortBy aExpr maybeAscDesc maybeNullsOrder -> do
-    squealExpr <- renderPGTAExpr aExpr
-    let
-      (asc, desc) = case maybeNullsOrder of
-        Nothing -> ('S.Asc, 'S.Desc)
-        Just PGT_AST.FirstNullsOrder -> ('S.AscNullsFirst, 'S.DescNullsFirst)
-        Just PGT_AST.LastNullsOrder -> ('S.AscNullsLast, 'S.DescNullsLast)
-
-    let
-      constructor = case maybeAscDesc of
-        Just PGT_AST.DescAscDesc -> desc
-        _ -> asc -- default to ASC
-    pure $ ConE constructor `AppE` squealExpr
-  PGT_AST.UsingSortBy{} -> fail "ORDER BY USING is not supported"
+{-# LANGUAGE GHC2021 #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE ExplicitForAll #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskellQuotes #-}
+{-# LANGUAGE ViewPatterns #-}
+
+-- | Description: Translate query expressions.
+module Squeal.QuasiQuotes.Query (
+  toSquealQuery,
+  renderPGTTableRef,
+  renderPGTTargeting,
+  renderPGTTargetList,
+  renderPGTAExpr,
+  getIdentText,
+) where
+
+import Control.Applicative (Alternative((<|>)))
+import Control.Monad (unless, when)
+import Data.Foldable (Foldable(elem, foldl'), foldlM)
+import Data.Maybe (fromMaybe, isJust, isNothing)
+import Data.String (IsString(fromString))
+import Language.Haskell.TH.Syntax
+  ( Exp(AppE, AppTypeE, ConE, InfixE, LabelE, ListE, LitE, TupE, VarE)
+  , Lit(IntegerL, StringL), TyLit(NumTyLit), Type(LitT), Name, Q, mkName
+  )
+import Prelude
+  ( Applicative(pure), Bool(False, True), Either(Left, Right), Eq((==))
+  , Foldable(foldr, length, null), Functor(fmap), Maybe(Just, Nothing)
+  , MonadFail(fail), Num((*), (+), (-), fromInteger), Ord((<), (>=))
+  , Semigroup((<>)), Show(show), Traversable(mapM), ($), (&&), (.), (<$>), (||)
+  , Int, Integer, any, either, error, fromIntegral, id, otherwise, zip
+  )
+import qualified Data.ByteString.Char8 as BS8
+import qualified Data.List.NonEmpty as NE
+import qualified Data.Text as Text
+import qualified PostgresqlSyntax.Ast as PGT_AST
+import qualified Squeal.PostgreSQL as S
+
+
+toSquealQuery
+  :: [Text.Text]
+  -> Maybe (NE.NonEmpty PGT_AST.Ident)
+  -> PGT_AST.SelectStmt
+  -> Q Exp
+toSquealQuery cteNames maybeColAliases selectStmt = case selectStmt of
+  Left selectNoParens -> toSquealSelectNoParens cteNames maybeColAliases selectNoParens
+  Right selectWithParens -> toSquealSelectWithParens cteNames maybeColAliases selectWithParens
+
+
+toSquealSelectWithParens
+  :: [Text.Text]
+  -> Maybe (NE.NonEmpty PGT_AST.Ident)
+  -> PGT_AST.SelectWithParens
+  -> Q Exp
+toSquealSelectWithParens cteNames maybeColAliases = \case
+  PGT_AST.NoParensSelectWithParens snp -> toSquealSelectNoParens cteNames maybeColAliases snp
+  PGT_AST.WithParensSelectWithParens swp ->
+    {- The AST structure itself should handle precedence.  Just recurse.  -}
+    toSquealSelectWithParens cteNames maybeColAliases swp
+
+
+toSquealSelectNoParens
+  :: [Text.Text]
+  -> Maybe (NE.NonEmpty PGT_AST.Ident)
+  -> PGT_AST.SelectNoParens
+  -> Q Exp
+toSquealSelectNoParens
+  initialCteNames
+  maybeColAliases
+  ( PGT_AST.SelectNoParens
+      maybeWithClause
+      selectClause
+      maybeSortClause
+      maybeSelectLimit
+      maybeForLockingClause
+    ) = do
+    (cteNames, renderedWithClause) <-
+      case maybeWithClause of
+        Nothing -> pure (initialCteNames, Nothing)
+        Just withClause -> do
+          (names, exp) <- renderPGTWithClause initialCteNames withClause
+          pure (names, Just exp)
+
+    squealQueryBody <-
+      case selectClause of
+        Left simpleSelect ->
+          toSquealSimpleSelect
+            cteNames
+            maybeColAliases
+            simpleSelect
+            maybeSortClause
+            maybeSelectLimit
+            maybeForLockingClause
+        Right selectWithParens' -> toSquealSelectWithParens cteNames maybeColAliases selectWithParens'
+
+    case renderedWithClause of
+      Nothing -> pure squealQueryBody
+      Just withExp -> pure $ VarE 'S.with `AppE` withExp `AppE` squealQueryBody
+
+
+renderPGTWithClause :: [Text.Text] -> PGT_AST.WithClause -> Q ([Text.Text], Exp)
+renderPGTWithClause initialCteNames (PGT_AST.WithClause recursive ctes) = do
+    when recursive $ fail "Recursive WITH clauses are not supported yet."
+    let
+      cteList = NE.toList ctes
+    (finalCteNames, renderedCtes) <-
+      foldlM
+        ( \(names, exps) cte -> do
+            (name, exp) <- renderCte names cte
+            pure (names <> [name], exps <> [exp])
+        )
+        (initialCteNames, [])
+        cteList
+
+    let
+      withExp =
+        foldr
+          (\cte acc -> ConE '(S.:>>) `AppE` cte `AppE` acc)
+          (ConE 'S.Done)
+          renderedCtes
+    pure (finalCteNames, withExp)
+  where
+    renderCte :: [Text.Text] -> PGT_AST.CommonTableExpr -> Q (Text.Text, Exp)
+    renderCte existingCteNames (PGT_AST.CommonTableExpr ident maybeColNames maybeMaterialized stmt) = do
+      when (isJust maybeColNames) $
+        fail "Column name lists in CTEs are not supported yet."
+      when (isJust maybeMaterialized) $
+        fail "MATERIALIZED/NOT MATERIALIZED for CTEs is not supported yet."
+      cteQueryExp <-
+        case stmt of
+          PGT_AST.SelectPreparableStmt selectStmt -> toSquealQuery existingCteNames Nothing selectStmt
+          _ -> fail "Only SELECT statements are supported in CTEs."
+      let
+        cteName = getIdentText ident
+      pure
+        (cteName, VarE 'S.as `AppE` cteQueryExp `AppE` LabelE (Text.unpack cteName))
+
+
+toSquealSimpleSelect
+  :: [Text.Text]
+  -> Maybe (NE.NonEmpty PGT_AST.Ident)
+  -> PGT_AST.SimpleSelect
+  -> Maybe PGT_AST.SortClause
+  -> Maybe PGT_AST.SelectLimit
+  -> Maybe PGT_AST.ForLockingClause
+  -> Q Exp
+toSquealSimpleSelect cteNames maybeColAliases simpleSelect maybeSortClause maybeSelectLimit maybeForLockingClause =
+  case simpleSelect of
+    PGT_AST.ValuesSimpleSelect valuesClause -> do
+      unless
+        ( isNothing maybeSortClause
+            && isNothing maybeSelectLimit
+            && isNothing maybeForLockingClause
+        )
+        $ fail
+        $ "ORDER BY / OFFSET / LIMIT / FOR UPDATE etc. not supported with VALUES clause "
+          <> "in this translation yet."
+      renderedValues <- renderValuesClauseToNP cteNames maybeColAliases valuesClause
+      pure $ VarE 'S.values_ `AppE` renderedValues
+    PGT_AST.NormalSimpleSelect
+      maybeTargeting
+      maybeIntoClause
+      maybeFromClause
+      maybeWhereClause
+      maybeGroupClause
+      maybeHavingClause
+      maybeWindowClause ->
+        do
+          targeting <-
+            case maybeTargeting of
+              Nothing ->
+                fail "SELECT without a selection list is not supported."
+              Just targeting -> pure targeting
+          case
+              ( maybeFromClause
+              , maybeGroupClause
+              , maybeHavingClause
+              , maybeIntoClause
+              , maybeSelectLimit
+              , maybeWhereClause
+              , maybeWindowClause
+              )
+            of
+              ( Nothing
+                , Nothing
+                , Nothing
+                , Nothing
+                , Nothing
+                , Nothing
+                , Nothing
+                ) ->
+                  do
+                    -- Case: SELECT <targeting_list> (no FROM, no other clauses)
+                    -- Translate to S.values_
+                    renderedTargetingForValues <-
+                      renderPGTTargetingForValues cteNames targeting
+                    pure $
+                      VarE 'S.values_ `AppE` renderedTargetingForValues
+              (Nothing, _, _, _, _, _, _) ->
+                {-
+                  Case: SELECT <targeting_list> (no FROM, but other
+                  clauses are present)
+                -}
+                fail $
+                  "SELECT with targeting but no FROM clause cannot have "
+                    <> "other clauses like INTO, WHERE, GROUP BY, HAVING, "
+                    <> "WINDOW, or LIMIT/OFFSET."
+              (Just fromClause, _, _, _, _, _, _) -> do
+                -- Case: SELECT ... FROM ... (original logic)
+
+                when (isJust maybeIntoClause) $
+                  fail "INTO clause is not yet supported in this translation."
+                when (isJust maybeWindowClause) $
+                  fail $
+                    "WINDOW clause is not yet supported in this translation "
+                      <> "for NormalSimpleSelect with FROM."
+
+                renderedFromClauseExp <- renderPGTTableRef cteNames fromClause
+                let
+                  baseTableExpr = VarE 'S.from `AppE` renderedFromClauseExp
+
+                tableExprWithWhere <-
+                  case maybeWhereClause of
+                    Nothing -> pure baseTableExpr
+                    Just wc -> do
+                      renderedWC <- renderPGTAExpr cteNames wc
+                      pure $
+                        InfixE
+                          (Just baseTableExpr)
+                          (VarE '(S.&))
+                          (Just (AppE (VarE 'S.where_) renderedWC))
+
+                tableExprWithGroupBy <-
+                  applyPGTGroupBy cteNames tableExprWithWhere maybeGroupClause
+
+                tableExprWithHaving <-
+                  case maybeHavingClause of
+                    Nothing -> pure tableExprWithGroupBy
+                    Just hc -> do
+                      when (isNothing maybeGroupClause) $
+                        fail "HAVING clause requires a GROUP BY clause."
+                      renderedHC <- renderPGTAExpr cteNames hc
+                      pure $
+                        InfixE
+                          (Just tableExprWithGroupBy)
+                          (VarE '(S.&))
+                          (Just (AppE (VarE 'S.having) renderedHC))
+
+                tableExprWithOrderBy <-
+                  case maybeSortClause of
+                    Nothing -> pure tableExprWithHaving
+                    Just sortClause -> do
+                      renderedSC <- renderPGTSortClause cteNames sortClause
+                      pure $
+                        InfixE
+                          (Just tableExprWithHaving)
+                          (VarE '(S.&))
+                          (Just (AppE (VarE 'S.orderBy) renderedSC))
+
+                (tableExprWithOffset, mTableExprWithLimit) <-
+                  processSelectLimit cteNames tableExprWithOrderBy maybeSelectLimit
+
+                let
+                  baseFinalTableExpr =
+                    fromMaybe tableExprWithOffset mTableExprWithLimit
+
+                -- Apply FOR LOCKING clause if present
+                finalTableExprWithPotentialLocking <-
+                  case maybeForLockingClause of
+                    Nothing -> pure baseFinalTableExpr
+                    Just flc -> do
+                      lockingClauseExps <- renderPGTForLockingClauseItems flc
+                      pure $
+                        foldl'
+                          ( \accTableExpr lockingClauseExp ->
+                              InfixE
+                                (Just accTableExpr)
+                                (VarE '(S.&))
+                                (Just (AppE (VarE 'S.lockRows) lockingClauseExp))
+                          )
+                          baseFinalTableExpr
+                          lockingClauseExps
+
+                (selectionTargetExp, maybeDistinctOnExprs) <-
+                  renderPGTTargeting cteNames targeting
+
+                squealSelectFn <-
+                  case maybeDistinctOnExprs of
+                    Nothing ->
+                      case targeting of
+                        PGT_AST.DistinctTargeting Nothing _ ->
+                          pure $ VarE 'S.selectDistinct
+                        _ -> pure $ VarE 'S.select -- Normal or ALL
+                    Just distinctOnAstExprs -> do
+                      distinctOnSquealSortExps <-
+                        renderPGTOnExpressionsClause cteNames distinctOnAstExprs
+                      pure $
+                        VarE 'S.selectDistinctOn
+                          `AppE` distinctOnSquealSortExps
+
+                pure $
+                  squealSelectFn
+                    `AppE` selectionTargetExp
+                    `AppE` finalTableExprWithPotentialLocking
+    unsupportedSimpleSelect ->
+      fail $
+        "Unsupported simple select statement: "
+          <> show unsupportedSimpleSelect
+
+
+-- Helper for VALUES clause: Assumes S.values_ for a single row of values.
+-- PGT_AST.ValuesClause is NonEmpty (NonEmpty PGT_AST.AExpr)
+renderValuesClauseToNP
+  :: [Text.Text]
+  -> Maybe (NE.NonEmpty PGT_AST.Ident)
+  -> PGT_AST.ValuesClause
+  -> Q Exp
+renderValuesClauseToNP cteNames maybeColAliases (firstRowExps NE.:| restRowExps) = do
+  unless (null restRowExps) $
+    fail $
+      "Multi-row VALUES clause requires S.values, this translation "
+        <> "currently supports single row S.values_."
+  convertRowToNP firstRowExps
+ where
+  colAliasTexts = fmap (fmap getIdentText . NE.toList) maybeColAliases
+
+  convertRowToNP :: NE.NonEmpty PGT_AST.AExpr -> Q Exp
+  convertRowToNP exprs = do
+    let
+      exprList = NE.toList exprs
+    aliasTexts <-
+      case colAliasTexts of
+        Just aliases ->
+          if length aliases == length exprList
+            then pure aliases
+            else
+              fail
+                "Number of column aliases in CTE does not match number of columns in VALUES clause."
+        Nothing -> pure $ fmap (Text.pack . ("_column" <>) . show) [1 :: Int ..]
+    go (zip exprList aliasTexts)
+   where
+    go :: [(PGT_AST.AExpr, Text.Text)] -> Q Exp
+    go [] = pure $ ConE 'S.Nil
+    go ((expr, aliasText) : fs) = do
+      renderedExpr <- renderPGTAExpr cteNames expr
+      let
+        aliasedExp = VarE 'S.as `AppE` renderedExpr `AppE` LabelE (Text.unpack aliasText)
+      restExp <- go fs
+      pure $ ConE '(S.:*) `AppE` aliasedExp `AppE` restExp
+
+
+renderPGTForLockingClauseItems :: PGT_AST.ForLockingClause -> Q [Exp]
+renderPGTForLockingClauseItems = \case
+  PGT_AST.ReadOnlyForLockingClause ->
+    fail $
+      "FOR READ ONLY is not supported as a row-level locking "
+        <> "clause by Squeal-QQ."
+  PGT_AST.ItemsForLockingClause itemsNe ->
+    mapM renderPGTForLockingItem (NE.toList itemsNe)
+
+
+renderPGTForLockingItem :: PGT_AST.ForLockingItem -> Q Exp
+renderPGTForLockingItem
+  ( PGT_AST.ForLockingItem
+      strength
+      maybeTables
+      waitingOpt
+    ) = do
+    squealStrength <- renderPGTForLockingStrength strength
+    squealTables <-
+      case maybeTables of
+        Nothing ->
+          {- Empty list for "OF" tables means all tables in query -}
+          pure $ ConE 'S.Nil
+        Just tablesNe -> do
+          aliasExps <-
+            mapM
+              ( \qn -> case qn of
+                  PGT_AST.SimpleQualifiedName ident ->
+                    pure $ LabelE (Text.unpack $ getIdentText ident)
+                  _ ->
+                    fail $
+                      "Qualified table names like schema.table in "
+                        <> "FOR UPDATE/SHARE OF clauses are not yet "
+                        <> "supported. Please use simple table aliases "
+                        <> "that refer to tables in the FROM clause."
+              )
+              (NE.toList tablesNe)
+          pure $
+            foldr
+              (\itemExp acc -> ConE '(S.:*) `AppE` itemExp `AppE` acc)
+              (ConE 'S.Nil)
+              aliasExps
+
+    squealWaiting <- renderPGTWaiting waitingOpt
+
+    pure $
+      ConE 'S.For `AppE` squealStrength `AppE` squealTables `AppE` squealWaiting
+
+
+renderPGTForLockingStrength :: PGT_AST.ForLockingStrength -> Q Exp
+renderPGTForLockingStrength = \case
+  PGT_AST.UpdateForLockingStrength -> pure $ ConE 'S.Update
+  PGT_AST.NoKeyUpdateForLockingStrength -> pure $ ConE 'S.NoKeyUpdate
+  PGT_AST.ShareForLockingStrength -> pure $ ConE 'S.Share
+  PGT_AST.KeyForLockingStrength -> pure $ ConE 'S.KeyShare
+
+
+renderPGTWaiting :: Maybe Bool -> Q Exp
+renderPGTWaiting = \case
+  Nothing -> pure $ ConE 'S.Wait -- Default (no NOWAIT or SKIP LOCKED)
+  Just False -> pure $ ConE 'S.NoWait -- NOWAIT
+  Just True -> pure $ ConE 'S.SkipLocked -- SKIP LOCKED
+
+
+applyPGTGroupBy :: [Text.Text] -> Exp -> Maybe PGT_AST.GroupClause -> Q Exp
+applyPGTGroupBy cteNames currentTableExpr = \case
+  Nothing -> pure currentTableExpr
+  Just groupClause -> do
+    renderedGB <- renderPGTGroupByClauseElements cteNames groupClause
+    pure $
+      InfixE
+        (Just currentTableExpr)
+        (VarE '(S.&))
+        (Just (AppE (VarE 'S.groupBy) renderedGB))
+
+
+renderPGTGroupByClauseElements :: [Text.Text] -> PGT_AST.GroupClause -> Q Exp
+renderPGTGroupByClauseElements cteNames = \case
+  PGT_AST.EmptyGroupingSetGroupByItem NE.:| [] ->
+    pure $ ConE 'S.Nil
+  groupByItems -> do
+    renderedExprs <- mapM (renderPGTGroupByItem cteNames) (NE.toList groupByItems)
+    pure $
+      foldr
+        (\expr acc -> ConE '(S.:*) `AppE` expr `AppE` acc)
+        (ConE 'S.Nil)
+        renderedExprs
+
+renderPGTGroupByItem :: [Text.Text] -> PGT_AST.GroupByItem -> Q Exp
+renderPGTGroupByItem cteNames = \case
+  PGT_AST.ExprGroupByItem scalarExpr -> renderPGTAExpr cteNames scalarExpr
+  PGT_AST.EmptyGroupingSetGroupByItem -> pure (ConE 'S.Nil)
+  unsupportedGroup ->
+    fail $
+      "Unsupported grouping expression: " <> show unsupportedGroup
+
+
+processSelectLimit :: [Text.Text] -> Exp -> Maybe PGT_AST.SelectLimit -> Q (Exp, Maybe Exp)
+processSelectLimit _cteNames tableExpr Nothing = pure (tableExpr, Nothing)
+processSelectLimit cteNames tableExpr (Just selectLimit) = do
+  let
+    (maybeOffsetClause, maybeLimitClause) = case selectLimit of
+      PGT_AST.LimitOffsetSelectLimit lim off -> (Just off, Just lim)
+      PGT_AST.OffsetLimitSelectLimit off lim -> (Just off, Just lim)
+      PGT_AST.LimitSelectLimit lim -> (Nothing, Just lim)
+      PGT_AST.OffsetSelectLimit off -> (Just off, Nothing)
+
+  tableExprWithOffset <-
+    case maybeOffsetClause of
+      Nothing -> pure tableExpr
+      Just offsetVal -> do
+        offsetExp <- renderPGTOffsetClause cteNames offsetVal
+        pure $
+          InfixE
+            (Just tableExpr)
+            (VarE '(S.&))
+            (Just (AppE (VarE 'S.offset) offsetExp))
+
+  case maybeLimitClause of
+    Nothing -> pure (tableExprWithOffset, Nothing)
+    Just limitVal -> do
+      limitExp <- renderPGTLimitClause cteNames limitVal
+      pure
+        ( tableExprWithOffset
+        , Just
+            ( InfixE
+                (Just tableExprWithOffset)
+                (VarE '(S.&))
+                (Just (AppE (VarE 'S.limit) limitExp))
+            )
+        )
+
+
+renderPGTLimitClause :: [Text.Text] -> PGT_AST.LimitClause -> Q Exp
+renderPGTLimitClause cteNames = \case
+  PGT_AST.LimitLimitClause slValue mOffsetVal -> do
+    when (isJust mOffsetVal) $
+      fail
+        "LIMIT with comma (e.g. LIMIT x, y) is not supported. Use separate LIMIT and OFFSET clauses."
+    case slValue of
+      PGT_AST.ExprSelectLimitValue
+        ( PGT_AST.CExprAExpr
+            ( PGT_AST.FuncCExpr
+                ( PGT_AST.ApplicationFuncExpr
+                    ( PGT_AST.FuncApplication
+                        (PGT_AST.TypeFuncName (PGT_AST.UnquotedIdent "inline"))
+                        ( Just
+                            ( PGT_AST.NormalFuncApplicationParams
+                                Nothing
+                                ( PGT_AST.ExprFuncArgExpr
+                                    ( PGT_AST.CExprAExpr
+                                        (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing))
+                                      )
+                                    NE.:| []
+                                  )
+                                Nothing
+                              )
+                          )
+                      )
+                    Nothing
+                    Nothing
+                    Nothing
+                  )
+              )
+          ) -> pure $ VarE (mkName (Text.unpack (getIdentText ident)))
+      PGT_AST.ExprSelectLimitValue
+        (PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) ->
+          if n >= 0
+            then pure (LitE (IntegerL (fromIntegral n)))
+            else fail $ "LIMIT value must be non-negative: " <> show n
+      PGT_AST.AllSelectLimitValue ->
+        fail "LIMIT ALL not supported in this translation."
+      PGT_AST.ExprSelectLimitValue expr -> renderPGTAExpr cteNames expr
+  PGT_AST.FetchOnlyLimitClause{} ->
+    fail "FETCH clause is not fully supported yet."
+
+
+renderPGTOffsetClause :: [Text.Text] -> PGT_AST.OffsetClause -> Q Exp
+renderPGTOffsetClause cteNames = \case
+  PGT_AST.ExprOffsetClause
+    ( PGT_AST.CExprAExpr
+        ( PGT_AST.FuncCExpr
+            ( PGT_AST.ApplicationFuncExpr
+                ( PGT_AST.FuncApplication
+                    (PGT_AST.TypeFuncName (PGT_AST.UnquotedIdent "inline"))
+                    ( Just
+                        ( PGT_AST.NormalFuncApplicationParams
+                            Nothing
+                            ( PGT_AST.ExprFuncArgExpr
+                                ( PGT_AST.CExprAExpr
+                                    (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing))
+                                  )
+                                NE.:| []
+                              )
+                            Nothing
+                          )
+                      )
+                  )
+                Nothing
+                Nothing
+                Nothing
+              )
+          )
+      ) -> pure $ VarE (mkName (Text.unpack (getIdentText ident)))
+  PGT_AST.ExprOffsetClause
+    (PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) ->
+      if n >= 0
+        then pure (LitE (IntegerL (fromIntegral n)))
+        else fail $ "OFFSET value must be non-negative: " <> show n
+  PGT_AST.ExprOffsetClause expr -> renderPGTAExpr cteNames expr
+  PGT_AST.FetchFirstOffsetClause{} ->
+    fail "OFFSET with FETCH FIRST clause is not supported yet."
+
+
+-- Helper to render a single TargetEl for S.values_
+-- Each expression must be aliased.
+renderPGTTargetElForValues :: [Text.Text] -> PGT_AST.TargetEl -> Int -> Q Exp
+renderPGTTargetElForValues cteNames targetEl idx = do
+  (exprAST, mUserAlias) <-
+    case targetEl of
+      PGT_AST.AliasedExprTargetEl e an -> pure (e, Just an)
+      PGT_AST.ImplicitlyAliasedExprTargetEl e an -> pure (e, Just an)
+      PGT_AST.ExprTargetEl e -> pure (e, Nothing)
+      PGT_AST.AsteriskTargetEl ->
+        fail "SELECT * is not supported unless there is a from clause."
+  renderedScalarExp <- renderPGTAExpr cteNames exprAST
+  let
+    aliasLabelStr =
+      case mUserAlias of
+        Just ident -> Text.unpack $ getIdentText ident
+        Nothing -> "_col" <> show idx -- Default alias for VALUES items
+  pure $ VarE 'S.as `AppE` renderedScalarExp `AppE` LabelE aliasLabelStr
+
+
+-- Helper to render a TargetList into an NP list for S.values_
+renderPGTTargetListForValues :: [Text.Text] -> PGT_AST.TargetList -> Q Exp
+renderPGTTargetListForValues cteNames (item NE.:| items) = do
+  renderedItems <-
+    mapM
+      (\(el, idx) -> renderPGTTargetElForValues cteNames el idx)
+      (zip (item : items) [1 ..])
+  -- Construct NP list: e1 :* e2 :* ... :* Nil
+  -- Each element in renderedItems is an Exp.
+  -- The result of the fold should be an Exp.
+  -- Then pure the final Exp.
+  pure $
+    foldr
+      (\hd acc -> ConE '(S.:*) `AppE` hd `AppE` acc)
+      (ConE 'S.Nil)
+      renderedItems
+
+
+-- New function to render Targeting specifically for S.values_
+renderPGTTargetingForValues :: [Text.Text] -> PGT_AST.Targeting -> Q Exp
+renderPGTTargetingForValues cteNames = \case
+  PGT_AST.NormalTargeting targetList -> renderPGTTargetListForValues cteNames targetList
+  PGT_AST.AllTargeting (Just targetList) ->
+    renderPGTTargetListForValues cteNames targetList
+  PGT_AST.AllTargeting Nothing ->
+    fail $
+      "SELECT * (ALL targeting without a list) is not supported "
+        <> "with VALUES clause."
+  PGT_AST.DistinctTargeting{} ->
+    -- Handles both DISTINCT and DISTINCT ON
+    fail $
+      "DISTINCT and DISTINCT ON queries are not supported with VALUES clause in "
+        <> "this translation."
+
+
+renderPGTOnExpressionsClause :: [Text.Text] -> [PGT_AST.AExpr] -> Q Exp
+renderPGTOnExpressionsClause cteNames exprs = do
+  renderedSortExps <- mapM renderToSortExpr exprs
+  pure $ ListE renderedSortExps
+ where
+  renderToSortExpr :: PGT_AST.AExpr -> Q Exp
+  renderToSortExpr astExpr = do
+    squealExpr <- renderPGTAExpr cteNames astExpr
+    -- For DISTINCT ON, the direction (ASC/DESC) and NULLS order
+    -- are typically specified in the ORDER BY clause.
+    -- Here, we default to ASC for the SortExpression constructor.
+    pure $ ConE 'S.Asc `AppE` squealExpr
+
+
+renderPGTSortClause :: [Text.Text] -> PGT_AST.SortClause -> Q Exp
+renderPGTSortClause cteNames sortBys = ListE <$> mapM (renderPGTSortBy cteNames) (NE.toList sortBys)
+
+
+renderPGTSortBy :: [Text.Text] -> PGT_AST.SortBy -> Q Exp
+renderPGTSortBy cteNames = \case
+  PGT_AST.AscDescSortBy aExpr maybeAscDesc maybeNullsOrder -> do
+    squealExpr <- renderPGTAExpr cteNames aExpr
+    let
+      (asc, desc) = case maybeNullsOrder of
+        Nothing -> ('S.Asc, 'S.Desc)
+        Just PGT_AST.FirstNullsOrder -> ('S.AscNullsFirst, 'S.DescNullsFirst)
+        Just PGT_AST.LastNullsOrder -> ('S.AscNullsLast, 'S.DescNullsLast)
+
+    let
+      constructor = case maybeAscDesc of
+        Just PGT_AST.DescAscDesc -> desc
+        _ -> asc -- default to ASC
+    pure $ ConE constructor `AppE` squealExpr
+  PGT_AST.UsingSortBy{} -> fail "ORDER BY USING is not supported"
+
+
+renderPGTTableRef :: [Text.Text] -> NE.NonEmpty PGT_AST.TableRef -> Q Exp
+renderPGTTableRef cteNames tableRefs = do
+  renderedTableRefs <- mapM (renderSingleTableRef cteNames) (NE.toList tableRefs)
+  case renderedTableRefs of
+    [] -> fail "Empty FROM clause" -- Should not happen with NonEmpty
+    (firstTbl : restTbls) ->
+      -- For FROM t1, t2, t3 Squeal uses: (table #t1) & also (table #t2) & also (table #t3)
+      -- S.also takes new item first, then accumulated.
+      -- So foldl' (\acc item -> VarE 'S.also `AppE` item `AppE` acc) firstTbl restTbls
+      -- However, Squeal's FromClause Additional instance is `also right left`, meaning `also new current`.
+      -- So `foldl (\current new -> VarE 'S.also `AppE` new `AppE` current) firstTbl restTbls` is correct.
+      pure $ foldl' (\acc tbl -> VarE 'S.also `AppE` tbl `AppE` acc) firstTbl restTbls
+
+
+renderSingleTableRef :: [Text.Text] -> PGT_AST.TableRef -> Q Exp
+renderSingleTableRef cteNames = \case
+  PGT_AST.RelationExprTableRef relationExpr maybeAliasClause sampleClause -> do
+    when (isJust sampleClause) $ fail "TABLESAMPLE clause is not supported yet."
+    renderPGTRelationExprTableRef cteNames relationExpr maybeAliasClause
+  PGT_AST.JoinTableRef joinedTable maybeAliasClause ->
+    -- If `maybeAliasClause` is Just, it means `(JOIN_TABLE) AS alias`.
+    -- Squeal's direct join combinators don't alias the *result* of the join.
+    -- This would require wrapping the join in a subquery.
+    -- For now, we'll fail if an alias is applied to a complex join structure directly.
+    -- Simple table references with aliases are handled by RelationExprTableRef.
+    case maybeAliasClause of
+      Just _ ->
+        fail
+          "Aliasing a JOIN clause directly is not supported. Consider a subquery: (SELECT * FROM ...) AS alias"
+      Nothing -> renderPGTJoinedTable cteNames joinedTable
+  -- PGT_AST.InParensTableRefTableRef was an incorrect pattern, removing it.
+  -- Parenthesized joins are handled by PGT_AST.InParensJoinedTable within renderPGTJoinedTable.
+  unsupported ->
+    fail $ "Unsupported TableRef type in renderSingleTableRef: " <> show unsupported
+
+
+renderPGTJoinedTable :: [Text.Text] -> PGT_AST.JoinedTable -> Q Exp
+renderPGTJoinedTable cteNames = \case
+  PGT_AST.InParensJoinedTable joinedTable -> renderPGTJoinedTable cteNames joinedTable
+  PGT_AST.MethJoinedTable joinMeth leftRef rightRef -> do
+    leftTableExp <- renderSingleTableRef cteNames leftRef
+    rightTableExp <- renderSingleTableRef cteNames rightRef
+    case joinMeth of
+      PGT_AST.QualJoinMeth maybeJoinType joinQual ->
+        case joinQual of
+          PGT_AST.OnJoinQual onConditionAExpr -> do
+            onConditionExp <- renderPGTAExpr cteNames onConditionAExpr
+            squealJoinFn <-
+              case maybeJoinType of
+                Just (PGT_AST.LeftJoinType _) -> pure $ VarE 'S.leftOuterJoin
+                Just (PGT_AST.RightJoinType _) -> pure $ VarE 'S.rightOuterJoin
+                Just (PGT_AST.FullJoinType _) -> pure $ VarE 'S.fullOuterJoin
+                Just PGT_AST.InnerJoinType -> pure $ VarE 'S.innerJoin
+                Nothing -> pure $ VarE 'S.innerJoin -- SQL JOIN (no type) is INNER JOIN
+                -- Change: Use S.& for join: leftTableExp & squealJoinFn rightTableExp onConditionExp
+            pure $
+              InfixE
+                (Just leftTableExp)
+                (VarE '(S.&))
+                (Just (squealJoinFn `AppE` rightTableExp `AppE` onConditionExp))
+          PGT_AST.UsingJoinQual _identsNE ->
+            fail "USING join qualification not yet supported"
+      PGT_AST.CrossJoinMeth ->
+        -- Change: Use S.& for crossJoin: leftTableExp & S.crossJoin rightTableExp
+        pure $
+          InfixE
+            (Just leftTableExp)
+            (VarE '(S.&))
+            (Just (VarE 'S.crossJoin `AppE` rightTableExp))
+      PGT_AST.NaturalJoinMeth _naturalJoinType ->
+        -- Squeal does not have direct high-level support for NATURAL JOIN.
+        -- These would typically be rewritten as INNER JOINs with USING clauses
+        -- or explicit ON conditions based on common column names.
+        -- This is complex to implement correctly in the QQ and might be error-prone.
+        fail "NATURAL JOIN is not supported by Squeal-QQ."
+
+
+renderPGTRelationExprTableRef
+  :: [Text.Text] -> PGT_AST.RelationExpr -> Maybe PGT_AST.AliasClause -> Q Exp
+renderPGTRelationExprTableRef cteNames relationExpr maybeAliasClause = do
+  (tableName, schemaName) <-
+    case relationExpr of
+      PGT_AST.SimpleRelationExpr (PGT_AST.SimpleQualifiedName ident) _ ->
+        pure (getIdentText ident, Nothing)
+      PGT_AST.SimpleRelationExpr
+        ( PGT_AST.IndirectedQualifiedName
+            schemaIdent
+            (NE.last -> PGT_AST.AttrNameIndirectionEl tableIdent)
+          )
+        _ ->
+          pure (getIdentText tableIdent, Just (getIdentText schemaIdent))
+      _ ->
+        fail $
+          "Unsupported relation expression: " <> show relationExpr
+
+  aliasStr <-
+    case maybeAliasClause of
+      Just (PGT_AST.AliasClause _ aliasIdent _) -> pure $ Text.unpack (getIdentText aliasIdent)
+      Nothing -> case relationExpr of -- Infer default alias if none provided
+        PGT_AST.SimpleRelationExpr (PGT_AST.SimpleQualifiedName ident) _ -> pure $ Text.unpack (getIdentText ident)
+        PGT_AST.SimpleRelationExpr
+          ( PGT_AST.IndirectedQualifiedName
+              _
+              (NE.last -> PGT_AST.AttrNameIndirectionEl ident)
+            )
+          _ -> pure $ Text.unpack (getIdentText ident)
+        _ ->
+          fail $
+            "Cannot determine default alias for relation expression: " <> show relationExpr
+
+  let
+    isCte = tableName `elem` cteNames
+    squealFn = if isCte then VarE 'S.common else VarE 'S.table
+
+  tableExpr <-
+    case schemaName of
+      Nothing -> pure $ LabelE (Text.unpack tableName)
+      Just schema ->
+        if isCte
+          then
+            fail "CTEs cannot be schema-qualified."
+          else
+            pure $
+              VarE '(S.!)
+                `AppE` LabelE (Text.unpack schema)
+                `AppE` LabelE (Text.unpack tableName)
+
+  pure $ squealFn `AppE` (VarE 'S.as `AppE` tableExpr `AppE` LabelE aliasStr)
+
+
+{- |
+  Translates the `Targeting` clause of a SQL SELECT statement from the
+  `postgresql-syntax` AST (`PGT_AST.Targeting`) into a Squeal representation.
+  The `Targeting` clause defines the list of expressions or columns to be
+  returned by the query (e.g., `*`, `col1`, `col2 AS alias`, `DISTINCT col3`).
+
+  The function returns a Template Haskell `Q` computation that, when run,
+  produces a pair:
+  1. `Exp`: A Template Haskell expression representing the Squeal selection list.
+     This could be `S.Star` for `SELECT *`, or a constructed Squeal expression
+     for a list of target elements (e.g., `expression1 :* expression2 :* S.Nil`).
+  2. `Maybe [PGT_AST.AExpr]`: This field is used to pass along the expressions
+     from a `DISTINCT ON (expr1, expr2, ...)` clause. If the query uses
+     `DISTINCT ON`, this will be `Just` containing the list of `PGT_AST.AExpr`
+     nodes representing `expr1, expr2, ...`. For all other types of targeting
+     (e.g., `SELECT DISTINCT col`, `SELECT col1, col2`, `SELECT *`), this
+     will be `Nothing`.
+
+  The function handles different kinds of targeting:
+  - `PGT_AST.NormalTargeting`: Standard `SELECT col1, col2, ...`
+  - `PGT_AST.AllTargeting`: `SELECT ALL ...` (often equivalent to normal select or `SELECT *`)
+  - `PGT_AST.DistinctTargeting`: `SELECT DISTINCT ...` or `SELECT DISTINCT ON (...) ...`
+
+  Returns (SquealSelectionListExp, Maybe DistinctOnAstExpressions)
+-}
+renderPGTTargeting
+  :: [Text.Text]
+  -> PGT_AST.Targeting
+  -> Q (Exp, Maybe [PGT_AST.AExpr])
+renderPGTTargeting cteNames = \case
+  PGT_AST.NormalTargeting targetList -> do
+    selListExp <- renderPGTTargetList cteNames targetList
+    pure (selListExp, Nothing)
+  PGT_AST.AllTargeting maybeTargetList -> do
+    selListExp <-
+      case maybeTargetList of
+        Nothing -> pure $ ConE 'S.Star -- SELECT ALL (which is like SELECT *)
+        Just tl -> renderPGTTargetList cteNames tl
+    pure (selListExp, Nothing)
+  PGT_AST.DistinctTargeting maybeOnExprs targetList -> do
+    selListExp <- renderPGTTargetList cteNames targetList
+    pure (selListExp, fmap NE.toList maybeOnExprs)
+
+
+renderPGTTargetEl :: [Text.Text] -> PGT_AST.TargetEl -> Maybe PGT_AST.Ident -> Int -> Q Exp
+renderPGTTargetEl cteNames targetEl mOuterAlias idx =
+  let
+    (exprAST, mInternalAlias) = case targetEl of
+      PGT_AST.AliasedExprTargetEl e an -> (e, Just an)
+      PGT_AST.ImplicitlyAliasedExprTargetEl e an -> (e, Just an)
+      PGT_AST.ExprTargetEl e -> (e, Nothing)
+      PGT_AST.AsteriskTargetEl ->
+        ( PGT_AST.CExprAExpr
+            ( PGT_AST.AexprConstCExpr
+                PGT_AST.NullAexprConst
+            )
+        , Nothing -- Placeholder for Star, should be S.Star
+        )
+    finalAliasName = mOuterAlias <|> mInternalAlias
+  in
+    case targetEl of
+      PGT_AST.AsteriskTargetEl -> pure $ ConE 'S.Star
+      _ -> do
+        renderedScalarExp <- renderPGTAExpr cteNames exprAST
+        case exprAST of
+          PGT_AST.CExprAExpr (PGT_AST.ColumnrefCExpr _)
+            | Nothing <- finalAliasName ->
+                pure renderedScalarExp
+          _ -> do
+            let
+              aliasLabelStr =
+                case finalAliasName of
+                  Just ident -> Text.unpack $ getIdentText ident
+                  Nothing -> "_col" <> show idx
+            pure $
+              VarE 'S.as
+                `AppE` renderedScalarExp
+                `AppE` LabelE aliasLabelStr
+
+
+renderPGTTargetList :: [Text.Text] -> PGT_AST.TargetList -> Q Exp
+renderPGTTargetList cteNames (item NE.:| items) = go (item : items) 1
+  where
+    isAsterisk :: PGT_AST.TargetEl -> Bool
+    isAsterisk PGT_AST.AsteriskTargetEl = True
+    isAsterisk _ = False
+
+    isDotStar :: PGT_AST.TargetEl -> Bool
+    isDotStar
+      ( PGT_AST.ExprTargetEl
+          ( PGT_AST.CExprAExpr
+              (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref _ (Just indirection)))
+            )
+        ) =
+        any isAllIndirectionEl (NE.toList indirection)
+    isDotStar _ = False
+
+    isAllIndirectionEl :: PGT_AST.IndirectionEl -> Bool
+    isAllIndirectionEl PGT_AST.AllIndirectionEl = True
+    isAllIndirectionEl _ = False
+
+    renderPGTTargetElDotStar :: PGT_AST.TargetEl -> Q Exp
+    renderPGTTargetElDotStar
+      ( PGT_AST.ExprTargetEl
+          ( PGT_AST.CExprAExpr
+              ( PGT_AST.ColumnrefCExpr
+                  ( PGT_AST.Columnref
+                      qualName
+                      _ -- indirectionOpt
+                    )
+                )
+            )
+        ) =
+        pure $
+          ConE 'S.DotStar
+            `AppE` (LabelE (Text.unpack (getIdentText qualName)))
+    renderPGTTargetElDotStar _ =
+      fail "renderPGTTargetElDotStar called with unexpected TargetEl"
+
+    go :: [PGT_AST.TargetEl] -> Int -> Q Exp
+    go [] _ = fail "Empty selection list items in go."
+    go [el] currentIdx = renderOne el currentIdx
+    go (el : more) currentIdx = do
+      renderedEl <- renderOne el currentIdx
+      restRendered <- go more (currentIdx + 1)
+      pure $ ConE 'S.Also `AppE` restRendered `AppE` renderedEl
+
+    renderOne :: PGT_AST.TargetEl -> Int -> Q Exp
+    renderOne el idx
+      | isAsterisk el = pure $ ConE 'S.Star
+      | isDotStar el = renderPGTTargetElDotStar el
+      | otherwise = renderPGTTargetEl cteNames el Nothing idx
+
+
+-- | Defines associativity of an operator.
+data Associativity = LeftAssoc | RightAssoc | NonAssoc
+  deriving stock (Eq, Show)
+
+
+-- | Holds details for a binary operator relevant to precedence restructuring.
+data OperatorDetails = OperatorDetails
+  { odConstructor :: PGT_AST.AExpr -> PGT_AST.AExpr -> PGT_AST.AExpr
+  , odPrecedence :: Int
+  , odAssociativity :: Associativity
+  }
+
+
+{- | Extracts components if the expression is a recognized binary operator.
+Higher precedence number means binds tighter.
+Based on PostgreSQL operator precedence.
+-}
+getOperatorDetails
+  :: PGT_AST.AExpr -> Maybe (PGT_AST.AExpr, OperatorDetails, PGT_AST.AExpr)
+getOperatorDetails = \case
+  PGT_AST.SymbolicBinOpAExpr l symOp r ->
+    let
+      details _op constr prec assoc = Just (l, OperatorDetails constr prec assoc, r)
+      mathDetails mathOp prec assoc =
+        details
+          (PGT_AST.MathSymbolicExprBinOp mathOp)
+          ( \l' r' -> PGT_AST.SymbolicBinOpAExpr l' (PGT_AST.MathSymbolicExprBinOp mathOp) r'
+          )
+          prec
+          assoc
+    in
+      case symOp of
+        PGT_AST.MathSymbolicExprBinOp PGT_AST.ArrowUpMathOp -> mathDetails PGT_AST.ArrowUpMathOp 8 LeftAssoc
+        -- \^ (exponentiation)
+        PGT_AST.MathSymbolicExprBinOp op
+          | op `elem` [PGT_AST.AsteriskMathOp, PGT_AST.SlashMathOp, PGT_AST.PercentMathOp] ->
+              mathDetails op 7 LeftAssoc
+        -- \* / %
+        PGT_AST.MathSymbolicExprBinOp op
+          | op `elem` [PGT_AST.PlusMathOp, PGT_AST.MinusMathOp] ->
+              mathDetails op 6 LeftAssoc -- binary + -
+        PGT_AST.MathSymbolicExprBinOp op -- Comparisons
+          | op
+              `elem` [ PGT_AST.ArrowLeftMathOp
+                     , PGT_AST.ArrowRightMathOp
+                     , PGT_AST.EqualsMathOp
+                     , PGT_AST.LessEqualsMathOp
+                     , PGT_AST.GreaterEqualsMathOp
+                     , PGT_AST.ArrowLeftArrowRightMathOp
+                     , PGT_AST.ExclamationEqualsMathOp
+                     ] ->
+              mathDetails op 3 LeftAssoc -- < > = <= >= <> !=
+        PGT_AST.QualSymbolicExprBinOp qualOp ->
+          -- User-defined operators, bitwise, etc.
+          details
+            (PGT_AST.QualSymbolicExprBinOp qualOp)
+            ( \l' r' -> PGT_AST.SymbolicBinOpAExpr l' (PGT_AST.QualSymbolicExprBinOp qualOp) r'
+            )
+            5
+            LeftAssoc
+        _ -> Nothing -- Should be exhaustive for PGT_AST.MathSymbolicExprBinOp if it's a binary op
+  PGT_AST.AndAExpr l r -> Just (l, OperatorDetails PGT_AST.AndAExpr 2 LeftAssoc, r) -- AND (precedence 2 in PG docs example)
+  PGT_AST.OrAExpr l r -> Just (l, OperatorDetails PGT_AST.OrAExpr 1 LeftAssoc, r) -- OR (precedence 1 in PG docs example)
+  PGT_AST.VerbalExprBinOpAExpr l notOp verbalOp r mEscape ->
+    -- LIKE, ILIKE, SIMILAR TO
+    Just
+      ( l
+      , OperatorDetails
+          (\l' r' -> PGT_AST.VerbalExprBinOpAExpr l' notOp verbalOp r' mEscape)
+          3
+          LeftAssoc
+      , r -- Same as comparisons
+      )
+  PGT_AST.ReversableOpAExpr l notOp (PGT_AST.DistinctFromAExprReversableOp r) ->
+    -- IS DISTINCT FROM
+    Just
+      ( l
+      , OperatorDetails
+          ( \l' r' ->
+              PGT_AST.ReversableOpAExpr l' notOp (PGT_AST.DistinctFromAExprReversableOp r')
+          )
+          3
+          LeftAssoc
+      , r -- Same as =
+      )
+  _ -> Nothing
+
+
+-- | Rearranges the AExpr syntax tree to account for operator precedence.
+fixOperatorPrecedence :: PGT_AST.AExpr -> PGT_AST.AExpr
+fixOperatorPrecedence = go
+  where
+    go expr =
+      case getOperatorDetails expr of
+        Just (l1, op1Details, r1) ->
+          let
+            l1Fixed = go l1
+            r1Fixed = go r1
+            currentOpConstructor = odConstructor op1Details
+            currentPrecedence = odPrecedence op1Details
+            currentAssociativity = odAssociativity op1Details
+          in
+            case getOperatorDetails r1Fixed of
+              Just (l2, op2Details, r2) ->
+                let
+                  -- We have effectively: l1Fixed `op1` (l2 `op2` r2)
+                  -- l2 is the left child of the (potentially restructured) r1Fixed
+                  -- r2 is the right child of the (potentially restructured) r1Fixed
+                  innerOpConstructor = odConstructor op2Details
+                  innerPrecedence = odPrecedence op2Details
+                in
+                  -- innerAssociativity = odAssociativity op2Details -- Not used in this branch's logic directly
+
+                  if currentPrecedence < innerPrecedence
+                    || (currentPrecedence == innerPrecedence && currentAssociativity == RightAssoc)
+                    then
+                      -- op2 binds tighter, or op1 is right-associative with same precedence.
+                      -- Structure l1Fixed `op1` (l2 `op2` r2) is correct.
+                      currentOpConstructor l1Fixed r1Fixed
+                    else
+                      -- op1 binds tighter, or op1 is left-associative with same precedence.
+                      -- We need to rotate to form: (l1Fixed `op1` l2) `op2` r2
+                      let
+                        newLeftChild = currentOpConstructor l1Fixed l2
+                      in
+                        go (innerOpConstructor newLeftChild r2) -- Recursively fix the new structure
+              Nothing ->
+                -- Right child r1Fixed is not a binary operator we're rebalancing.
+                -- The structure l1Fixed `op1` r1Fixed is locally correct.
+                currentOpConstructor l1Fixed r1Fixed
+        Nothing ->
+          -- Current expression `expr` is not a binary operator handled by getOperatorDetails,
+          -- or it's an atom. Recursively fix its children.
+          case expr of
+            PGT_AST.CExprAExpr c -> PGT_AST.CExprAExpr c -- CExprs are atoms or structured (FuncCExpr, CaseCExpr etc.)
+            PGT_AST.TypecastAExpr e t -> PGT_AST.TypecastAExpr (go e) t
+            PGT_AST.CollateAExpr e c -> PGT_AST.CollateAExpr (go e) c
+            PGT_AST.AtTimeZoneAExpr e1 e2 -> PGT_AST.AtTimeZoneAExpr (go e1) (go e2)
+            PGT_AST.PlusAExpr e -> PGT_AST.PlusAExpr (go e) -- Unary plus
+            -- MinusAExpr is handled by fixOperatorPrecedence if it's part of a binary op,
+            -- otherwise it's a unary negate.
+            PGT_AST.MinusAExpr e -> PGT_AST.MinusAExpr (go e)
+            PGT_AST.PrefixQualOpAExpr op e -> PGT_AST.PrefixQualOpAExpr op (go e)
+            PGT_AST.SuffixQualOpAExpr e op -> PGT_AST.SuffixQualOpAExpr (go e) op
+            PGT_AST.NotAExpr e -> PGT_AST.NotAExpr (go e)
+            PGT_AST.ReversableOpAExpr e notFlag revOp ->
+              let
+                eFixed = go e
+              in
+                case revOp of
+                  PGT_AST.DistinctFromAExprReversableOp{} -> expr -- Should have been caught by getOperatorDetails
+                  PGT_AST.BetweenAExprReversableOp symm bExpr aExpr ->
+                    PGT_AST.ReversableOpAExpr
+                      eFixed
+                      notFlag
+                      (PGT_AST.BetweenAExprReversableOp symm (goBExpr bExpr) (go aExpr))
+                  PGT_AST.InAExprReversableOp inExpr ->
+                    PGT_AST.ReversableOpAExpr
+                      eFixed
+                      notFlag
+                      (PGT_AST.InAExprReversableOp (goInExpr inExpr))
+                  _ -> PGT_AST.ReversableOpAExpr eFixed notFlag revOp -- For IS NULL, IS TRUE etc.
+            PGT_AST.IsnullAExpr e -> PGT_AST.IsnullAExpr (go e)
+            PGT_AST.NotnullAExpr e -> PGT_AST.NotnullAExpr (go e)
+            PGT_AST.OverlapsAExpr row1 row2 -> PGT_AST.OverlapsAExpr (goRow row1) (goRow row2)
+            PGT_AST.SubqueryAExpr e op st sub ->
+              PGT_AST.SubqueryAExpr
+                (go e)
+                op
+                st
+                (either (Left . goSelectWithParens) (Right . go) sub)
+            PGT_AST.UniqueAExpr s -> PGT_AST.UniqueAExpr (goSelectWithParens s)
+            PGT_AST.DefaultAExpr -> PGT_AST.DefaultAExpr
+            _ -> expr -- Leaf node or unhandled construct
+    goBExpr :: PGT_AST.BExpr -> PGT_AST.BExpr
+    goBExpr = \case
+      PGT_AST.CExprBExpr c -> PGT_AST.CExprBExpr c
+      PGT_AST.TypecastBExpr be t -> PGT_AST.TypecastBExpr (goBExpr be) t
+      PGT_AST.PlusBExpr be -> PGT_AST.PlusBExpr (goBExpr be)
+      PGT_AST.MinusBExpr be -> PGT_AST.MinusBExpr (goBExpr be)
+      -- BExpr's own binary ops are typically higher precedence than AExpr's,
+      -- but for completeness, one could define getOperatorDetails for BExpr too.
+      -- For now, just recurse.
+      PGT_AST.SymbolicBinOpBExpr l op r -> PGT_AST.SymbolicBinOpBExpr (goBExpr l) op (goBExpr r)
+      PGT_AST.QualOpBExpr op be -> PGT_AST.QualOpBExpr op (goBExpr be)
+      PGT_AST.IsOpBExpr be notFlag isOp ->
+        let
+          beFixed = goBExpr be
+        in
+          case isOp of
+            PGT_AST.DistinctFromBExprIsOp b ->
+              PGT_AST.IsOpBExpr beFixed notFlag (PGT_AST.DistinctFromBExprIsOp (goBExpr b))
+            _ -> PGT_AST.IsOpBExpr beFixed notFlag isOp
+
+    goRow :: PGT_AST.Row -> PGT_AST.Row
+    goRow = \case
+      PGT_AST.ExplicitRowRow mExprs -> PGT_AST.ExplicitRowRow (fmap (NE.map go) mExprs)
+      PGT_AST.ImplicitRowRow (PGT_AST.ImplicitRow exprs aexpr) -> PGT_AST.ImplicitRowRow (PGT_AST.ImplicitRow (NE.map go exprs) (go aexpr))
+
+    goSelectWithParens :: PGT_AST.SelectWithParens -> PGT_AST.SelectWithParens
+    goSelectWithParens = id -- Placeholder: A full traversal would be needed.
+    goInExpr :: PGT_AST.InExpr -> PGT_AST.InExpr
+    goInExpr = \case
+      PGT_AST.SelectInExpr s -> PGT_AST.SelectInExpr (goSelectWithParens s)
+      PGT_AST.ExprListInExpr exprs -> PGT_AST.ExprListInExpr (NE.map go exprs)
+
+
+renderPGTAExpr :: [Text.Text] -> PGT_AST.AExpr -> Q Exp
+renderPGTAExpr cteNames astExpr = case fixOperatorPrecedence astExpr of
+  PGT_AST.CExprAExpr cExpr -> renderPGTCExpr cteNames cExpr
+  PGT_AST.TypecastAExpr aExpr typename -> do
+    tnExp <- renderPGTTypename typename
+    aExp <- renderPGTAExpr cteNames aExpr
+    pure $ VarE 'S.cast `AppE` tnExp `AppE` aExp
+  PGT_AST.SymbolicBinOpAExpr left op right -> do
+    lExp <- renderPGTAExpr cteNames left
+    rExp <- renderPGTAExpr cteNames right
+    squealOpExp <-
+      case op of
+        PGT_AST.MathSymbolicExprBinOp mathOp -> pure $ renderPGTMathOp mathOp
+        PGT_AST.QualSymbolicExprBinOp qualOp -> pure $ renderPGTQualOp qualOp
+    pure (squealOpExp `AppE` lExp `AppE` rExp)
+  PGT_AST.PrefixQualOpAExpr op expr -> do
+    let
+      opExp' = renderPGTQualOp op
+    eExp' <- renderPGTAExpr cteNames expr
+    pure (opExp' `AppE` eExp')
+  PGT_AST.AndAExpr left right -> do
+    lExp' <- renderPGTAExpr cteNames left
+    rExp' <- renderPGTAExpr cteNames right
+    pure (VarE '(S..&&) `AppE` lExp' `AppE` rExp')
+  PGT_AST.OrAExpr left right -> do
+    lExp' <- renderPGTAExpr cteNames left
+    rExp' <- renderPGTAExpr cteNames right
+    pure (VarE '(S..||) `AppE` lExp' `AppE` rExp')
+  PGT_AST.NotAExpr expr -> do
+    eExp' <- renderPGTAExpr cteNames expr
+    pure (VarE 'S.not_ `AppE` eExp')
+  PGT_AST.VerbalExprBinOpAExpr left not op right mEscape -> do
+    when (isJust mEscape) $ fail "LIKE with ESCAPE is not supported yet."
+    lExp' <- renderPGTAExpr cteNames left
+    rExp' <- renderPGTAExpr cteNames right
+    baseOpExp <-
+      case op of
+        PGT_AST.LikeVerbalExprBinOp -> pure $ VarE 'S.like
+        PGT_AST.IlikeVerbalExprBinOp -> pure $ VarE 'S.ilike
+        _ -> fail $ "Unsupported verbal binary operator: " <> show op
+    let
+      finalOpExp = if not then VarE 'S.not_ `AppE` baseOpExp else baseOpExp
+    pure (finalOpExp `AppE` lExp' `AppE` rExp')
+  PGT_AST.ReversableOpAExpr expr not reversableOp -> do
+    renderedExpr' <- renderPGTAExpr cteNames expr
+    case reversableOp of
+      PGT_AST.NullAExprReversableOp ->
+        pure $ (if not then VarE 'S.isNotNull else VarE 'S.isNull) `AppE` renderedExpr'
+      PGT_AST.BetweenAExprReversableOp _asymmetric bExpr andAExpr -> do
+        bExp' <- renderPGTBExpr cteNames bExpr
+        aExp' <- renderPGTAExpr cteNames andAExpr
+        let
+          opVar' = if not then VarE 'S.notBetween else VarE 'S.between
+        pure $ opVar' `AppE` renderedExpr' `AppE` TupE [Just bExp', Just aExp']
+      PGT_AST.InAExprReversableOp inExpr ->
+        case inExpr of
+          PGT_AST.ExprListInExpr exprList -> do
+            let opVar' = if not then VarE 'S.notIn else VarE 'S.in_
+            listExp' <- ListE <$> mapM (renderPGTAExpr cteNames) (NE.toList exprList)
+            pure $ opVar' `AppE` renderedExpr' `AppE` listExp'
+          PGT_AST.SelectInExpr selectWithParens -> do
+            let
+              (squealOp, squealFn) =
+                if not
+                  then (VarE '(S../=), VarE 'S.subAll)
+                  else (VarE '(S..==), VarE 'S.subAny)
+            subqueryExp <- toSquealSelectWithParens cteNames Nothing selectWithParens
+            pure $ squealFn `AppE` renderedExpr' `AppE` squealOp `AppE` subqueryExp
+      _ -> fail $ "Unsupported reversable operator: " <> show reversableOp
+  PGT_AST.DefaultAExpr -> pure $ ConE 'S.Default
+  PGT_AST.MinusAExpr expr -> do
+    -- Unary minus
+    eExp' <- renderPGTAExpr cteNames expr
+    let
+      zeroExp = AppE (VarE 'fromInteger) (LitE (IntegerL 0))
+    pure (InfixE (Just zeroExp) (VarE '(-)) (Just eExp'))
+  unsupported -> fail $ "Unsupported AExpr: " <> show unsupported
+
+
+renderPGTBExpr :: [Text.Text] -> PGT_AST.BExpr -> Q Exp
+renderPGTBExpr cteNames = \case
+  PGT_AST.CExprBExpr cExpr -> renderPGTCExpr cteNames cExpr
+  PGT_AST.TypecastBExpr bExpr typename -> do
+    tnExp <- renderPGTTypename typename
+    bExp <- renderPGTBExpr cteNames bExpr
+    pure $ VarE 'S.cast `AppE` tnExp `AppE` bExp
+  PGT_AST.SymbolicBinOpBExpr left op right -> do
+    lExp <- renderPGTBExpr cteNames left
+    rExp <- renderPGTBExpr cteNames right
+    squealOpExp <-
+      case op of
+        PGT_AST.MathSymbolicExprBinOp mathOp -> pure $ renderPGTMathOp mathOp
+        PGT_AST.QualSymbolicExprBinOp qualOp -> pure $ renderPGTQualOp qualOp
+    pure (squealOpExp `AppE` lExp `AppE` rExp)
+  unsupported -> fail $ "Unsupported BExpr: " <> show unsupported
+
+
+renderPGTCExpr :: [Text.Text] -> PGT_AST.CExpr -> Q Exp
+renderPGTCExpr cteNames = \case
+  PGT_AST.AexprConstCExpr aexprConst -> pure $ renderPGTAexprConst aexprConst
+  PGT_AST.ColumnrefCExpr columnref -> pure $ renderPGTColumnref columnref
+  PGT_AST.ParamCExpr n maybeIndirection -> do
+    when (isJust maybeIndirection) $
+      fail "Parameters with indirection (e.g. $1[i]) are not supported."
+    pure $ VarE 'S.param `AppTypeE` LitT (NumTyLit (fromIntegral n))
+  PGT_AST.InParensCExpr expr maybeIndirection -> do
+    when (isJust maybeIndirection) $
+      fail "Parenthesized expressions with indirection are not supported."
+    renderPGTAExpr cteNames expr -- Squeal's operator precedence should handle this
+  PGT_AST.FuncCExpr funcExpr -> renderPGTFuncExpr cteNames funcExpr
+  unsupported -> fail $ "Unsupported CExpr: " <> show unsupported
+
+
+renderPGTFuncExpr :: [Text.Text] -> PGT_AST.FuncExpr -> Q Exp
+renderPGTFuncExpr cteNames = \case
+  PGT_AST.ApplicationFuncExpr funcApp maybeWithinGroup maybeFilter maybeOver -> do
+    when (isJust maybeWithinGroup) $ fail "WITHIN GROUP clause is not supported."
+    when (isJust maybeFilter) $ fail "FILTER clause is not supported."
+    when (isJust maybeOver) $ fail "OVER clause is not supported."
+    renderPGTFuncApplication cteNames funcApp
+  PGT_AST.SubexprFuncExpr funcCommonSubexpr -> renderPGTFuncExprCommonSubexpr cteNames funcCommonSubexpr
+
+
+renderPGTFuncApplication :: [Text.Text] -> PGT_AST.FuncApplication -> Q Exp
+renderPGTFuncApplication cteNames (PGT_AST.FuncApplication funcName maybeParams) =
+  case funcName of
+    PGT_AST.IndirectedFuncName{} ->
+      fail "Functions with indirection (e.g. schema.func) are not supported."
+    PGT_AST.TypeFuncName fident ->
+      let
+        fnNameStr = Text.unpack (getIdentText fident)
+      in
+        case Text.toLower (Text.pack fnNameStr) of
+          "inline" ->
+            case maybeParams of
+              Just (PGT_AST.NormalFuncApplicationParams _ args _) ->
+                case NE.toList args of
+                  [ PGT_AST.ExprFuncArgExpr
+                      (PGT_AST.CExprAExpr (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing)))
+                    ] -> do
+                      let
+                        varName :: Name
+                        varName = mkName . Text.unpack . getIdentText $ ident
+                      pure $ VarE 'S.inline `AppE` VarE varName
+                  _ -> fail "inline() function expects a single variable argument"
+              _ -> fail "inline() function expects a single variable argument"
+          "inline_param" ->
+            case maybeParams of
+              Just (PGT_AST.NormalFuncApplicationParams _ args _) ->
+                case NE.toList args of
+                  [ PGT_AST.ExprFuncArgExpr
+                      (PGT_AST.CExprAExpr (PGT_AST.ColumnrefCExpr (PGT_AST.Columnref ident Nothing)))
+                    ] -> do
+                      let
+                        varName :: Name
+                        varName = mkName . Text.unpack . getIdentText $ ident
+                      pure $ VarE 'S.inlineParam `AppE` VarE varName
+                  _ -> fail "inline_param() function expects a single variable argument"
+              _ -> fail "inline_param() function expects a single variable argument"
+          otherFnName ->
+            let
+              squealFn :: Q Exp
+              squealFn =
+                case otherFnName of
+                  "coalesce" -> pure $ VarE 'S.coalesce
+                  "lower" -> pure $ VarE 'S.lower
+                  "char_length" -> pure $ VarE 'S.charLength
+                  "character_length" -> pure $ VarE 'S.charLength
+                  "upper" -> pure $ VarE 'S.upper
+                  "count" -> pure $ VarE 'S.count -- Special handling for count(*) might be needed
+                  "now" -> pure $ VarE 'S.now
+                  _ -> fail $ "Unsupported function: " <> fnNameStr
+            in
+              case maybeParams of
+                Nothing -> squealFn -- No-argument function
+                Just params -> case params of
+                  PGT_AST.NormalFuncApplicationParams maybeAllOrDistinct args maybeSortClause -> do
+                    when (isJust maybeAllOrDistinct) $
+                      fail "DISTINCT in function calls is not supported."
+                    when (isJust maybeSortClause) $
+                      fail "ORDER BY in function calls is not supported."
+                    fn <- squealFn
+                    argExps <- mapM (renderPGTFuncArgExpr cteNames) (NE.toList args)
+                    pure $ foldl' AppE fn argExps
+                  PGT_AST.StarFuncApplicationParams ->
+                    -- Specific for count(*)
+                    if fnNameStr == "count"
+                      then pure $ VarE 'S.countStar
+                      else fail "Star argument only supported for COUNT"
+                  _ -> fail $ "Unsupported function parameters structure: " <> show params
+
+
+renderPGTFuncArgExpr :: [Text.Text] -> PGT_AST.FuncArgExpr -> Q Exp
+renderPGTFuncArgExpr cteNames = \case
+  PGT_AST.ExprFuncArgExpr aExpr -> renderPGTAExpr cteNames aExpr
+  _ -> fail "Named or colon-syntax function arguments not supported"
+
+
+renderPGTFuncExprCommonSubexpr :: [Text.Text] -> PGT_AST.FuncExprCommonSubexpr -> Q Exp
+renderPGTFuncExprCommonSubexpr cteNames = \case
+  PGT_AST.CurrentTimestampFuncExprCommonSubexpr (Just _) ->
+    fail "CURRENT_TIMESTAMP with precision is not supported."
+  PGT_AST.CurrentTimestampFuncExprCommonSubexpr Nothing -> pure $ VarE 'S.now -- Or S.currentTimestamp
+  PGT_AST.CurrentDateFuncExprCommonSubexpr -> pure $ VarE 'S.currentDate
+  PGT_AST.CoalesceFuncExprCommonSubexpr exprListNE -> do
+    renderedInitExprs <- mapM (renderPGTAExpr cteNames) (NE.init exprListNE)
+    renderedLastExpr <- renderPGTAExpr cteNames (NE.last exprListNE)
+    pure $ VarE 'S.coalesce `AppE` ListE renderedInitExprs `AppE` renderedLastExpr
+  e -> fail $ "Unsupported common function subexpression: " <> show e
+
+
+renderPGTColumnref :: PGT_AST.Columnref -> Exp
+renderPGTColumnref (PGT_AST.Columnref colId maybeIndirection) =
+    case maybeIndirection of
+      Nothing -> LabelE (Text.unpack (getIdentText colId))
+      Just indirection ->
+        let
+          base = LabelE (Text.unpack (getIdentText colId))
+        in
+          foldl' applyIndirection base (NE.toList indirection)
+  where
+    applyIndirection acc = \case
+      PGT_AST.AttrNameIndirectionEl attrName ->
+        VarE '(S.!) `AppE` acc `AppE` LabelE (Text.unpack (getIdentText attrName))
+      _ -> error "Unsupported column reference indirection"
+
+
+renderPGTAexprConst :: PGT_AST.AexprConst -> Exp
+renderPGTAexprConst = \case
+  PGT_AST.IAexprConst n ->
+    ConE 'S.UnsafeExpression
+      `AppE` ( VarE 'BS8.pack
+                 `AppE` LitE (StringL (show n))
+             )
+  PGT_AST.FAexprConst f ->
+    ConE 'S.UnsafeExpression
+      `AppE` ( VarE 'BS8.pack
+                 `AppE` LitE (StringL (show f))
+             )
+  PGT_AST.SAexprConst s ->
+    VarE 'fromString `AppE` LitE (StringL (Text.unpack s))
+  PGT_AST.BoolAexprConst True -> VarE 'S.true
+  PGT_AST.BoolAexprConst False -> VarE 'S.false
+  PGT_AST.NullAexprConst -> VarE 'S.null_
+  unsupported -> error $ "Unsupported AexprConst: " <> show unsupported
+
+
+renderPGTMathOp :: PGT_AST.MathOp -> Exp
+renderPGTMathOp = \case
+  PGT_AST.PlusMathOp -> VarE '(+)
+  PGT_AST.MinusMathOp -> VarE '(-)
+  PGT_AST.AsteriskMathOp -> VarE '(*)
+  PGT_AST.EqualsMathOp -> VarE '(S..==)
+  PGT_AST.ArrowLeftArrowRightMathOp -> VarE '(S../=) -- <>
+  PGT_AST.ExclamationEqualsMathOp -> VarE '(S../=) -- !=
+  PGT_AST.ArrowRightMathOp -> VarE '(S..>)
+  PGT_AST.GreaterEqualsMathOp -> VarE '(S..>=)
+  PGT_AST.ArrowLeftMathOp -> VarE '(S..<)
+  PGT_AST.LessEqualsMathOp -> VarE '(S..<=)
+  _ -> error "Unsupported math operator"
+
+
+renderPGTQualOp :: PGT_AST.QualOp -> Exp
+renderPGTQualOp = \case
+  PGT_AST.OpQualOp opText ->
+    case Text.toLower opText of
+      "+" -> VarE '(+)
+      "-" -> VarE '(-)
+      "*" -> VarE '(*)
+      "=" -> VarE '(S..==)
+      "<>" -> VarE '(S../=)
+      "!=" -> VarE '(S../=)
+      ">" -> VarE '(S..>)
+      ">=" -> VarE '(S..>=)
+      "<" -> VarE '(S..<)
+      "<=" -> VarE '(S..<=)
+      "and" -> VarE '(S..&&)
+      "or" -> VarE '(S..||)
+      "not" -> VarE 'S.not_
+      "like" -> VarE 'S.like
+      "ilike" -> VarE 'S.ilike
+      _ -> error $ "Unsupported QualOp operator text: " <> Text.unpack opText
+  PGT_AST.OperatorQualOp _anyOperator ->
+    error "OPERATOR(any_operator) syntax not supported"
+
+
+renderPGTTypename :: PGT_AST.Typename -> Q Exp
+renderPGTTypename (PGT_AST.Typename setof simpleTypename nullable arrayInfo) = do
+  when setof $ fail "SETOF type modifier is not supported."
+  when nullable $ fail "Nullable type modifier '?' is not supported."
+  baseTypeExp <- renderPGTSimpleTypename simpleTypename
+  case arrayInfo of
+    Nothing -> pure baseTypeExp
+    Just (dims, nullableArray) -> do
+      when nullableArray $ fail "Nullable array modifier '?' is not supported."
+      renderPGTArrayDimensions baseTypeExp dims
+
+
+renderPGTArrayDimensions :: Exp -> PGT_AST.TypenameArrayDimensions -> Q Exp
+renderPGTArrayDimensions baseTypeExp = \case
+  PGT_AST.BoundsTypenameArrayDimensions bounds ->
+    -- Squeal's fixarray takes a type-level list of Nats for dimensions.
+    -- This is hard to represent directly from parsed integer bounds.
+    -- For now, we'll only support 1D arrays if bounds are provided.
+    case NE.toList bounds of
+      [Just dim] ->
+        pure $
+          VarE 'S.fixarray
+            `AppTypeE` LitT (NumTyLit (fromIntegral dim))
+            `AppE` baseTypeExp
+      [_] -> pure $ VarE 'S.vararray `AppE` baseTypeExp -- e.g. int[]
+      _ ->
+        fail "Multidimensional arrays with explicit bounds not yet supported"
+  PGT_AST.ExplicitTypenameArrayDimensions Nothing -> pure $ VarE 'S.vararray `AppE` baseTypeExp -- e.g. sometype ARRAY
+  PGT_AST.ExplicitTypenameArrayDimensions (Just dim) ->
+    pure $
+      VarE 'S.fixarray
+        `AppTypeE` LitT (NumTyLit (fromIntegral dim))
+        `AppE` baseTypeExp -- e.g. sometype ARRAY[N]
+
+
+renderPGTSimpleTypename :: PGT_AST.SimpleTypename -> Q Exp
+renderPGTSimpleTypename = \case
+  PGT_AST.GenericTypeSimpleTypename
+    (PGT_AST.GenericType typeFnName attrs maybeModifiers) -> do
+      when (isJust attrs) $
+        fail "Qualified type names (e.g. schema.my_type) are not supported."
+      let
+        nameLower = Text.toLower (getIdentText typeFnName)
+        extractLength :: Maybe PGT_AST.TypeModifiers -> Q Integer
+        extractLength = \case
+          Just
+            ((PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) NE.:| []) -> pure (fromIntegral n)
+          Just other ->
+            fail $
+              "Unsupported type modifier for " <> Text.unpack nameLower <> ": " <> show other
+          Nothing ->
+            fail $
+              "Type "
+                <> Text.unpack nameLower
+                <> " requires a length argument (e.g., "
+                <> Text.unpack nameLower
+                <> "(N))."
+
+        extractLengthOrDefault :: Integer -> Maybe PGT_AST.TypeModifiers -> Q Integer
+        extractLengthOrDefault def = \case
+          Just
+            ((PGT_AST.CExprAExpr (PGT_AST.AexprConstCExpr (PGT_AST.IAexprConst n))) NE.:| []) -> pure (fromIntegral n)
+          Just other ->
+            fail $
+              "Unsupported type modifier for " <> Text.unpack nameLower <> ": " <> show other
+          Nothing -> pure def
+      case nameLower of
+        "char" -> do
+          len <- extractLengthOrDefault 1 maybeModifiers
+          pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit len)
+        "character" -> do
+          len <- extractLengthOrDefault 1 maybeModifiers
+          pure $ VarE 'S.character `AppTypeE` LitT (NumTyLit len)
+        "varchar" -> case maybeModifiers of
+          Nothing -> pure $ VarE 'S.text -- varchar without length is text
+          Just _ -> do
+            len <- extractLength maybeModifiers
+            pure $ VarE 'S.varchar `AppTypeE` LitT (NumTyLit len)
+        "character varying" -> case maybeModifiers of
+          Nothing -> pure $ VarE 'S.text -- character varying without length is text
+          Just _ -> do
+            len <- extractLength maybeModifiers
+            pure $ VarE 'S.characterVarying `AppTypeE` LitT (NumTyLit len)
+        "bool" -> pure $ VarE 'S.bool
+        "int2" -> pure $ VarE 'S.int2
+        "smallint" -> pure $ VarE 'S.smallint
+        "int4" -> pure $ VarE 'S.int4
+        "int" -> pure $ VarE 'S.int
+        "integer" -> pure $ VarE 'S.integer
+        "int8" -> pure $ VarE 'S.int8
+        "bigint" -> pure $ VarE 'S.bigint
+        "numeric" -> pure $ VarE 'S.numeric -- Ignoring precision/scale for now
+        "float4" -> pure $ VarE 'S.float4 -- Ignoring precision for now
+        "real" -> pure $ VarE 'S.real
+        "float8" -> pure $ VarE 'S.float8
+        "double precision" -> pure $ VarE 'S.doublePrecision
+        "money" -> pure $ VarE 'S.money
+        "text" -> pure $ VarE 'S.text
+        "bytea" -> pure $ VarE 'S.bytea
+        "timestamp" -> pure $ VarE 'S.timestamp
+        "timestamptz" -> pure $ VarE 'S.timestamptz
+        "timestamp with time zone" -> pure $ VarE 'S.timestampWithTimeZone
+        "date" -> pure $ VarE 'S.date
+        "time" -> pure $ VarE 'S.time
+        "timetz" -> pure $ VarE 'S.timetz
+        "time with time zone" -> pure $ VarE 'S.timeWithTimeZone
+        "interval" -> pure $ VarE 'S.interval
+        "uuid" -> pure $ VarE 'S.uuid
+        "inet" -> pure $ VarE 'S.inet
+        "json" -> pure $ VarE 'S.json
+        "jsonb" -> pure $ VarE 'S.jsonb
+        "tsvector" -> pure $ VarE 'S.tsvector
+        "tsquery" -> pure $ VarE 'S.tsquery
+        "oid" -> pure $ VarE 'S.oid
+        "int4range" -> pure $ VarE 'S.int4range
+        "int8range" -> pure $ VarE 'S.int8range
+        "numrange" -> pure $ VarE 'S.numrange
+        "tsrange" -> pure $ VarE 'S.tsrange
+        "tstzrange" -> pure $ VarE 'S.tstzrange
+        "daterange" -> pure $ VarE 'S.daterange
+        "record" -> pure $ VarE 'S.record
+        other -> fail $ "Unsupported generic type name: " <> Text.unpack other
+  PGT_AST.NumericSimpleTypename numeric -> renderPGTNumeric numeric
+  PGT_AST.BitSimpleTypename (PGT_AST.Bit _varying _maybeLength) ->
+    -- PostgreSQL's BIT type without length is BIT(1). BIT VARYING without length is unlimited.
+    -- Squeal's `char` and `varchar` are for text, not bit strings.
+    -- Squeal does not have a direct equivalent for PG bit string types yet.
+    -- Potentially map to bytea or text, or add new Squeal types. For now, error.
+    fail
+      "BIT and BIT VARYING types are not directly supported by Squeal's `char`/`varchar` like types. Consider using bytea or text, or a custom Squeal type."
+  PGT_AST.CharacterSimpleTypename charTypeAst ->
+    case charTypeAst of
+      PGT_AST.CharacterCharacter False -> pure $ VarE 'S.character `AppTypeE` LitT (NumTyLit 1) -- SQL CHARACTER -> Squeal character(1)
+      PGT_AST.CharacterCharacter True -> pure $ VarE 'S.text -- SQL CHARACTER VARYING -> Squeal text
+      PGT_AST.CharCharacter False -> pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit 1) -- SQL CHAR -> Squeal char(1)
+      PGT_AST.CharCharacter True -> pure $ VarE 'S.text -- SQL CHAR VARYING -> Squeal text
+      PGT_AST.VarcharCharacter -> pure $ VarE 'S.text -- SQL VARCHAR (no length) -> Squeal text
+      -- National character types are often aliases for standard character types in PostgreSQL
+      PGT_AST.NationalCharacterCharacter False -> pure $ VarE 'S.character `AppTypeE` LitT (NumTyLit 1) -- NCHAR -> character(1)
+      PGT_AST.NationalCharacterCharacter True -> pure $ VarE 'S.text -- NCHAR VARYING -> text
+      PGT_AST.NationalCharCharacter False -> pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit 1) -- NATIONAL CHAR -> char(1)
+      PGT_AST.NationalCharCharacter True -> pure $ VarE 'S.text -- NATIONAL CHAR VARYING -> text
+      PGT_AST.NcharCharacter False -> pure $ VarE 'S.char `AppTypeE` LitT (NumTyLit 1) -- NCHAR (synonym for NATIONAL CHAR) -> char(1)
+      PGT_AST.NcharCharacter True -> pure $ VarE 'S.text -- NCHAR VARYING -> text
+  PGT_AST.ConstDatetimeSimpleTypename dt -> case dt of
+    PGT_AST.TimestampConstDatetime precision maybeTimezone -> do
+      when (isJust precision) $ fail "TIMESTAMP with precision is not supported."
+      pure $ case maybeTimezone of
+        Just False -> VarE 'S.timestampWithTimeZone -- WITH TIME ZONE
+        _ -> VarE 'S.timestamp -- WITHOUT TIME ZONE or unspecified
+    PGT_AST.TimeConstDatetime precision maybeTimezone -> do
+      when (isJust precision) $ fail "TIME with precision is not supported."
+      pure $ case maybeTimezone of
+        Just False -> VarE 'S.timeWithTimeZone -- WITH TIME ZONE
+        _ -> VarE 'S.time -- WITHOUT TIME ZONE or unspecified
+  PGT_AST.ConstIntervalSimpleTypename (Left (Just _)) ->
+    fail "INTERVAL with qualifiers is not supported."
+  PGT_AST.ConstIntervalSimpleTypename (Left Nothing) ->
+    pure $ VarE 'S.interval
+  PGT_AST.ConstIntervalSimpleTypename (Right _) ->
+    fail "INTERVAL with integer literal is not supported in this context."
+
+
+renderPGTNumeric :: PGT_AST.Numeric -> Q Exp
+renderPGTNumeric = \case
+  PGT_AST.IntNumeric -> pure $ VarE 'S.int
+  PGT_AST.IntegerNumeric -> pure $ VarE 'S.integer
+  PGT_AST.SmallintNumeric -> pure $ VarE 'S.smallint
+  PGT_AST.BigintNumeric -> pure $ VarE 'S.bigint
+  PGT_AST.RealNumeric -> pure $ VarE 'S.real
+  PGT_AST.FloatNumeric (Just _) -> fail "FLOAT with precision is not supported."
+  PGT_AST.FloatNumeric Nothing -> pure $ VarE 'S.float4
+  PGT_AST.DoublePrecisionNumeric -> pure $ VarE 'S.doublePrecision
+  PGT_AST.DecimalNumeric (Just _) -> fail "DECIMAL with precision/scale is not supported."
+  PGT_AST.DecimalNumeric Nothing -> pure $ VarE 'S.numeric
+  PGT_AST.DecNumeric (Just _) -> fail "DEC with precision/scale is not supported."
+  PGT_AST.DecNumeric Nothing -> pure $ VarE 'S.numeric
+  PGT_AST.NumericNumeric (Just _) -> fail "NUMERIC with precision/scale is not supported."
+  PGT_AST.NumericNumeric Nothing -> pure $ VarE 'S.numeric
+  PGT_AST.BooleanNumeric -> pure $ VarE 'S.bool
+
+
+getIdentText :: PGT_AST.Ident -> Text.Text
+getIdentText = \case
+  PGT_AST.QuotedIdent t -> t
+  PGT_AST.UnquotedIdent t -> t
 
 
diff --git a/src/Squeal/QuasiQuotes/Update.hs b/src/Squeal/QuasiQuotes/Update.hs
--- a/src/Squeal/QuasiQuotes/Update.hs
+++ b/src/Squeal/QuasiQuotes/Update.hs
@@ -8,16 +8,18 @@
   toSquealUpdate,
 ) where
 
-import Data.Text (Text)
 import Control.Monad (when)
+import Data.Foldable (Foldable(foldr), foldlM)
 import Data.Maybe (isJust)
+import Data.Text (Text)
 import Language.Haskell.TH.Syntax (Exp(AppE, ConE, LabelE, VarE), Q)
 import Prelude
-  ( Applicative(pure), Foldable(foldr), Maybe(Just, Nothing), MonadFail(fail)
+  ( Applicative(pure), Maybe(Just, Nothing), MonadFail(fail), Semigroup((<>))
   , Traversable(mapM), ($), (<$>)
   )
-import Squeal.QuasiQuotes.Common
+import Squeal.QuasiQuotes.Query
   ( getIdentText, renderPGTAExpr, renderPGTTableRef, renderPGTTargetList
+  , toSquealQuery
   )
 import qualified Data.List.NonEmpty as NE
 import qualified Data.Text as Text
@@ -25,6 +27,44 @@
 import qualified Squeal.PostgreSQL as S
 
 
+renderPGTWithClause :: PGT_AST.WithClause -> Q ([Text.Text], Exp)
+renderPGTWithClause (PGT_AST.WithClause recursive ctes) = do
+    when recursive $ fail "Recursive WITH clauses are not supported yet."
+    let
+      cteList = NE.toList ctes
+    (finalCteNames, renderedCtes) <-
+      foldlM
+        ( \(names, exps) cte -> do
+            (name, exp) <- renderCte names cte
+            pure (names <> [name], exps <> [exp])
+        )
+        ([], [])
+        cteList
+
+    let
+      withExp =
+        foldr
+          (\cte acc -> ConE '(S.:>>) `AppE` cte `AppE` acc)
+          (ConE 'S.Done)
+          renderedCtes
+    pure (finalCteNames, withExp)
+  where
+    renderCte :: [Text.Text] -> PGT_AST.CommonTableExpr -> Q (Text.Text, Exp)
+    renderCte existingCteNames (PGT_AST.CommonTableExpr ident maybeColNames maybeMaterialized stmt) = do
+      when (isJust maybeMaterialized) $
+        fail "MATERIALIZED/NOT MATERIALIZED for CTEs is not supported yet."
+      cteStmtExp <-
+        case stmt of
+          PGT_AST.SelectPreparableStmt selectStmt -> do
+            queryExp <- toSquealQuery existingCteNames maybeColNames selectStmt
+            pure $ VarE 'S.queryStatement `AppE` queryExp
+          _ -> fail "Only SELECT statements are supported in CTEs for now."
+      let
+        cteName = getIdentText ident
+      pure
+        (cteName, VarE 'S.as `AppE` cteStmtExp `AppE` LabelE (Text.unpack cteName))
+
+
 toSquealUpdate :: PGT_AST.UpdateStmt -> Q Exp
 toSquealUpdate
   ( PGT_AST.UpdateStmt
@@ -35,38 +75,48 @@
       maybeWhereClause
       maybeReturningClause
     ) = do
-    when (isJust maybeWithClause) $
-      fail "WITH clauses are not supported in UPDATE statements yet."
+    (cteNames, renderedWithClause) <-
+      case maybeWithClause of
+        Nothing -> pure ([], Nothing)
+        Just withClause -> do
+          (names, exp) <- renderPGTWithClause withClause
+          pure (names, Just exp)
     targetTableExp <- renderPGTRelationExprOptAlias' relationExprOptAlias
 
-    setClauseExp <- renderPGTSetClauseList setClauseList
+    setClauseExp <- renderPGTSetClauseList cteNames setClauseList
 
     usingClauseExp <-
       case maybeFromClause of
         Nothing -> pure $ ConE 'S.NoUsing
-        Just fromClause -> AppE (ConE 'S.Using) <$> renderPGTTableRef fromClause
+        Just fromClause -> AppE (ConE 'S.Using) <$> renderPGTTableRef cteNames fromClause
 
     whereConditionExp <-
       case maybeWhereClause of
         Nothing ->
           fail
             "UPDATE statements must have a WHERE clause for safety. Use WHERE TRUE to update all rows."
-        Just (PGT_AST.ExprWhereOrCurrentClause whereAExpr) -> renderPGTAExpr whereAExpr
+        Just (PGT_AST.ExprWhereOrCurrentClause whereAExpr) -> renderPGTAExpr cteNames whereAExpr
         Just (PGT_AST.CursorWhereOrCurrentClause _) -> fail "WHERE CURRENT OF is not supported."
 
     returningClauseExp <-
       case maybeReturningClause of
         Nothing -> pure $ ConE 'S.Returning_ `AppE` ConE 'S.Nil
-        Just returningClause -> AppE (ConE 'S.Returning) <$> renderPGTTargetList returningClause
+        Just returningClause -> AppE (ConE 'S.Returning) <$> renderPGTTargetList cteNames returningClause
 
-    pure $
-      ( VarE 'S.update
-          `AppE` targetTableExp
-          `AppE` setClauseExp
-          `AppE` usingClauseExp
-          `AppE` whereConditionExp
-          `AppE` returningClauseExp
-      )
+    let
+      updateBody =
+        ( VarE 'S.update
+            `AppE` targetTableExp
+            `AppE` setClauseExp
+            `AppE` usingClauseExp
+            `AppE` whereConditionExp
+            `AppE` returningClauseExp
+        )
+    let
+      finalExp = case renderedWithClause of
+        Nothing -> updateBody
+        Just withExp -> VarE 'S.with `AppE` withExp `AppE` updateBody
+    pure finalExp
 
 
 renderPGTRelationExprOptAlias' :: PGT_AST.RelationExprOptAlias -> Q Exp
@@ -103,9 +153,9 @@
   pure $ VarE 'S.as `AppE` qualifiedAlias `AppE` LabelE (Text.unpack aliasName)
 
 
-renderPGTSetClauseList :: PGT_AST.SetClauseList -> Q Exp
-renderPGTSetClauseList setClauses = do
-  renderedItems <- mapM renderPGTSetClause (NE.toList setClauses)
+renderPGTSetClauseList :: [Text.Text] -> PGT_AST.SetClauseList -> Q Exp
+renderPGTSetClauseList cteNames setClauses = do
+  renderedItems <- mapM (renderPGTSetClause cteNames) (NE.toList setClauses)
   pure $
     foldr
       (\item acc -> ConE '(S.:*) `AppE` item `AppE` acc)
@@ -113,13 +163,14 @@
       renderedItems
 
 
-renderPGTSetClause :: PGT_AST.SetClause -> Q Exp
-renderPGTSetClause = \case
+renderPGTSetClause :: [Text.Text] -> PGT_AST.SetClause -> Q Exp
+renderPGTSetClause cteNames = \case
   PGT_AST.TargetSetClause (PGT_AST.SetTarget colId maybeIndirection) aExpr -> do
     when (isJust maybeIndirection) $
       fail "UPDATE SET with indirection (e.g., array access) is not supported."
-    let colNameStr = Text.unpack (getIdentText colId)
-    renderedExpr <- renderPGTAExpr aExpr
+    let
+      colNameStr = Text.unpack (getIdentText colId)
+    renderedExpr <- renderPGTAExpr cteNames aExpr
     pure $
       VarE 'S.as
         `AppE` (ConE 'S.Set `AppE` renderedExpr)
diff --git a/test/test.hs b/test/test.hs
--- a/test/test.hs
+++ b/test/test.hs
@@ -169,7 +169,7 @@
           squealRendering = "SELECT * FROM \"users\" AS \"users\" WHERE (\"name\" = (E'bob' :: text))"
         checkStatement squealRendering statement
 
-      it "select user.name from users" $ do
+      it "select users.name from users" $ do
         let
           statement :: Statement DB () (Field "name" Text, ())
           statement = [ssql| select users.name from users |]
@@ -413,7 +413,7 @@
         checkStatement squealRendering statement
 
       it
-        "select users.id from users left outer join emails on users.id == emails.user_id"
+        "select users.id from users left outer join emails on emails.user_id = users.id"
         $ do
           let
             statement
@@ -436,7 +436,7 @@
           checkStatement squealRendering statement
 
       it
-        "select * from users left outer join emails on users.id == emails.user_id"
+        "select users.id, users.name, emails.email from users left outer join emails on emails.user_id = users.id where emails.email = inline(\"targetEmail\")"
         $ do
           let
             targetEmail :: Text
@@ -558,6 +558,61 @@
               "SELECT \"employee_id\" AS \"employee_id\", \"name\" AS \"name\", count(ALL \"id\") AS \"_col3\" FROM \"users\" AS \"users\" GROUP BY \"employee_id\", \"name\""
           checkStatement squealRendering statement
 
+      describe "common table expressions" $ do
+        it "with users_cte as (select * from users) select * from users_cte" $ do
+          let
+            statement
+              :: Statement
+                   DB
+                   ()
+                   ( Field "id" Text
+                   , ( Field "name" Text
+                     , ( Field "employee_id" UUID
+                       , ( Field "bio" (Maybe Text)
+                         , ()
+                         )
+                       )
+                     )
+                   )
+            statement = [ssql| with users_cte as (select * from users) select * from users_cte |]
+            squealRendering :: Text
+            squealRendering =
+              "WITH \"users_cte\" AS (SELECT * FROM \"users\" AS \"users\") SELECT * FROM \"users_cte\" AS \"users_cte\""
+          checkStatement squealRendering statement
+
+        it
+          "with users_cte as (select * from users), emails_cte as (select * from emails) select users_cte.*, emails_cte.email from users_cte join emails_cte on users_cte.id = emails_cte.user_id"
+          $ do
+            let
+              statement
+                :: Statement
+                     DB
+                     ()
+                     ( Field "id" Text
+                     , ( Field "name" Text
+                       , ( Field "employee_id" UUID
+                         , ( Field "bio" (Maybe Text)
+                           , ( Field "email" (Maybe Text)
+                             , ()
+                             )
+                           )
+                         )
+                       )
+                     )
+              statement =
+                [ssql|
+                  with
+                    users_cte as (select * from users),
+                    emails_cte as (select * from emails)
+                  select users_cte.*, emails_cte.email
+                  from users_cte
+                  join emails_cte on users_cte.id = emails_cte.user_id
+                |]
+              squealRendering :: Text
+              squealRendering =
+                "WITH \"users_cte\" AS (SELECT * FROM \"users\" AS \"users\"), \"emails_cte\" AS (SELECT * FROM \"emails\" AS \"emails\") SELECT \"users_cte\".*, \"emails_cte\".\"email\" AS \"email\" FROM \"users_cte\" AS \"users_cte\" INNER JOIN \"emails_cte\" AS \"emails_cte\" ON (\"users_cte\".\"id\" = \"emails_cte\".\"user_id\")"
+            checkStatement squealRendering statement
+
     describe "inserts" $ do
       it "insert into emails (id, user_id, email) values (1, 'user-1', 'foo@bar')" $ do
         let
@@ -615,7 +670,7 @@
         checkStatement squealRendering statement
 
       it
-        "insert into emails (id, user_id, email) values (inline(i), inline(uid), inline(e))"
+        "insert into emails (id, user_id, email) values (inline(i), inline(uid), inline_param(e))"
         $ do
           let
             mkStatement :: Int32 -> Text -> Maybe Text -> Statement DB () ()
@@ -821,7 +876,7 @@
                      )
               statement =
                 [ssql|
-                insert into
+                  insert into
                   emails (id, user_id, email)
                   values (1, 'user-1', 'foo@bar')
                 returning *
@@ -831,6 +886,23 @@
                 "INSERT INTO \"emails\" AS \"emails\" (\"id\", \"user_id\", \"email\") VALUES (1, (E'user-1' :: text), (E'foo@bar' :: text)) RETURNING *"
             checkStatement squealRendering statement
 
+      describe "with common table expressions" $ do
+        it
+          "with new_user (id, name, bio) as (values ('id_new', 'new_name', 'new_bio')) insert into users_copy select * from new_user"
+          $ do
+            let
+              statement :: Statement DB () ()
+              statement =
+                [ssql|
+                  with new_user (id, name, bio) as (values ('id_new', 'new_name', 'new_bio'))
+                  insert into users_copy
+                  select * from new_user
+                |]
+              squealRendering :: Text
+              squealRendering =
+                "WITH \"new_user\" AS (SELECT * FROM (VALUES ((E'id_new' :: text), (E'new_name' :: text), (E'new_bio' :: text))) AS t (\"id\", \"name\", \"bio\")) INSERT INTO \"users_copy\" AS \"users_copy\" SELECT * FROM \"new_user\" AS \"new_user\""
+            checkStatement squealRendering statement
+
     describe "deletes" $ do
       it "delete from users where true" $ do
         let
@@ -870,6 +942,40 @@
             "DELETE FROM \"users\" AS \"users\" WHERE (\"id\" = (E'some-id' :: text)) RETURNING \"id\" AS \"id\""
         checkStatement squealRendering statement
 
+      describe "with common table expressions" $ do
+        it
+          "with to_delete as (select id from users where name = 'Alice') delete from users where id in (select to_delete.id from to_delete)"
+          $ do
+            let
+              statement :: Statement DB () ()
+              statement =
+                [ssql|
+                  with to_delete as (select id from users where name = 'Alice')
+                  delete from users
+                  where id in (select to_delete.id from to_delete)
+                |]
+              squealRendering :: Text
+              squealRendering =
+                "WITH \"to_delete\" AS (SELECT \"id\" AS \"id\" FROM \"users\" AS \"users\" WHERE (\"name\" = (E'Alice' :: text))) DELETE FROM \"users\" AS \"users\" WHERE (\"id\" = ANY (SELECT \"to_delete\".\"id\" AS \"id\" FROM \"to_delete\" AS \"to_delete\"))"
+            checkStatement squealRendering statement
+
+        it
+          "with to_delete as (select id from users where name = 'Alice') delete from users using to_delete where users.id = to_delete.id"
+          $ do
+            let
+              statement :: Statement DB () ()
+              statement =
+                [ssql|
+                  with to_delete as (select id from users where name = 'Alice')
+                  delete from users
+                  using to_delete
+                  where users.id = to_delete.id
+                |]
+              squealRendering :: Text
+              squealRendering =
+                "WITH \"to_delete\" AS (SELECT \"id\" AS \"id\" FROM \"users\" AS \"users\" WHERE (\"name\" = (E'Alice' :: text))) DELETE FROM \"users\" AS \"users\" USING \"to_delete\" AS \"to_delete\" WHERE (\"users\".\"id\" = \"to_delete\".\"id\")"
+            checkStatement squealRendering statement
+
     describe "updates" $ do
       it "update users set name = 'new name' where id = 'some-id'" $ do
         let
@@ -911,9 +1017,28 @@
             "UPDATE \"users\" AS \"users\" SET \"name\" = (E'new name' :: text) WHERE (\"id\" = (E'some-id' :: text)) RETURNING \"id\" AS \"id\""
         checkStatement squealRendering statement
 
+      describe "with common table expressions" $ do
+        it
+          "with to_update as (select id from users where name = 'Alice') update users set name = 'Alicia' from to_update where users.id = to_update.id"
+          $ do
+            let
+              statement :: Statement DB () ()
+              statement =
+                [ssql|
+                  with to_update as (select id from users where name = 'Alice')
+                  update users
+                  set name = 'Alicia'
+                  from to_update
+                  where users.id = to_update.id
+                |]
+              squealRendering :: Text
+              squealRendering =
+                "WITH \"to_update\" AS (SELECT \"id\" AS \"id\" FROM \"users\" AS \"users\" WHERE (\"name\" = (E'Alice' :: text))) UPDATE \"users\" AS \"users\" SET \"name\" = (E'Alicia' :: text) FROM \"to_update\" AS \"to_update\" WHERE (\"users\".\"id\" = \"to_update\".\"id\")"
+            checkStatement squealRendering statement
+
     describe "scalar expressions" $ do
       -- Binary Operators
-      it "select id != 'no-such-user' as neq from users" $ do
+      it "select users.id != 'no-such-user' as neq from users" $ do
         let
           stmt :: Statement DB () (Field "neq" (Maybe Bool), ())
           stmt = [ssql| select users.id != 'no-such-user' as neq from users |]
@@ -923,7 +1048,7 @@
               <> "\"neq\" FROM \"users\" AS \"users\""
         checkStatement squealRendering stmt
 
-      it "select * from users where id <> 'no-such-user'" $ do
+      it "select * from users where users.id <> 'no-such-user'" $ do
         let
           stmt
             :: Statement
@@ -944,7 +1069,7 @@
             "SELECT * FROM \"users\" AS \"users\" WHERE (\"users\".\"id\" <> (E'no-such-user' :: text))"
         checkStatement squealRendering stmt
 
-      it "select * from emails where id > 0" $ do
+      it "select * from emails where emails.id > 0" $ do
         let
           stmt
             :: Statement
@@ -956,7 +1081,7 @@
           squealRendering = "SELECT * FROM \"emails\" AS \"emails\" WHERE (\"emails\".\"id\" > 0)"
         checkStatement squealRendering stmt
 
-      it "select * from emails where id >= 0" $ do
+      it "select * from emails where emails.id >= 0" $ do
         let
           stmt
             :: Statement
@@ -968,7 +1093,7 @@
           squealRendering = "SELECT * FROM \"emails\" AS \"emails\" WHERE (\"emails\".\"id\" >= 0)"
         checkStatement squealRendering stmt
 
-      it "select * from emails where id < 10" $ do
+      it "select * from emails where emails.id < 10" $ do
         let
           stmt
             :: Statement
@@ -980,7 +1105,7 @@
           squealRendering = "SELECT * FROM \"emails\" AS \"emails\" WHERE (\"emails\".\"id\" < 10)"
         checkStatement squealRendering stmt
 
-      it "select * from emails where id <= 10" $ do
+      it "select * from emails where emails.id <= 10" $ do
         let
           stmt
             :: Statement
@@ -992,7 +1117,7 @@
           squealRendering = "SELECT * FROM \"emails\" AS \"emails\" WHERE (\"emails\".\"id\" <= 10)"
         checkStatement squealRendering stmt
 
-      it "select id + 1 as plus_one from emails" $ do
+      it "select emails.id + 1 as plus_one from emails" $ do
         let
           stmt :: Statement DB () (Field "plus_one" Int32, ())
           stmt = [ssql| select emails.id + 1 as plus_one from emails |]
@@ -1001,7 +1126,7 @@
             "SELECT (\"emails\".\"id\" + 1) AS \"plus_one\" FROM \"emails\" AS \"emails\""
         checkStatement squealRendering stmt
 
-      it "select id - 1 as minus_one from emails" $ do
+      it "select emails.id - 1 as minus_one from emails" $ do
         let
           stmt :: Statement DB () (Field "minus_one" Int32, ())
           stmt = [ssql| select emails.id - 1 as minus_one from emails |]
@@ -1010,7 +1135,7 @@
             "SELECT (\"emails\".\"id\" - 1) AS \"minus_one\" FROM \"emails\" AS \"emails\""
         checkStatement squealRendering stmt
 
-      it "select id * 2 as times_two from emails" $ do
+      it "select emails.id * 2 as times_two from emails" $ do
         let
           stmt :: Statement DB () (Field "times_two" Int32, ())
           stmt = [ssql| select emails.id * 2 as times_two from emails |]
@@ -1019,7 +1144,7 @@
             "SELECT (\"emails\".\"id\" * 2) AS \"times_two\" FROM \"emails\" AS \"emails\""
         checkStatement squealRendering stmt
 
-      it "select * from users where id = 'a' and name = 'b'" $ do
+      it "select * from users where users.id = 'a' and users.name = 'b'" $ do
         let
           stmt
             :: Statement
@@ -1034,7 +1159,7 @@
             "SELECT * FROM \"users\" AS \"users\" WHERE ((\"users\".\"id\" = (E'a' :: text)) AND (\"users\".\"name\" = (E'b' :: text)))"
         checkStatement squealRendering stmt
 
-      it "select * from users where id = 'a' or name = 'b'" $ do
+      it "select * from users where users.id = 'a' or users.name = 'b'" $ do
         let
           stmt
             :: Statement
@@ -1049,7 +1174,7 @@
             "SELECT * FROM \"users\" AS \"users\" WHERE ((\"users\".\"id\" = (E'a' :: text)) OR (\"users\".\"name\" = (E'b' :: text)))"
         checkStatement squealRendering stmt
 
-      it "select * from users where name like 'A%'" $ do
+      it "select * from users where users.name like 'A%'" $ do
         let
           stmt
             :: Statement
@@ -1064,7 +1189,7 @@
             "SELECT * FROM \"users\" AS \"users\" WHERE (\"users\".\"name\" LIKE (E'A%' :: text))"
         checkStatement squealRendering stmt
 
-      it "select * from users where name ilike 'a%'" $ do
+      it "select * from users where users.name ilike 'a%'" $ do
         let
           stmt
             :: Statement
@@ -1080,7 +1205,7 @@
         checkStatement squealRendering stmt
 
       -- Prefix Operators
-      it "select * from users where not (name = 'no-one')" $ do
+      it "select * from users where not (users.name = 'no-one')" $ do
         let
           stmt
             :: Statement
@@ -1095,7 +1220,7 @@
             "SELECT * FROM \"users\" AS \"users\" WHERE (NOT (\"users\".\"name\" = (E'no-one' :: text)))"
         checkStatement squealRendering stmt
 
-      it "select -id as neg_id from emails" $ do
+      it "select -emails.id as neg_id from emails" $ do
         let
           stmt :: Statement DB () (Field "neg_id" Int32, ())
           stmt = [ssql| select -emails.id as neg_id from emails |]
@@ -1105,7 +1230,7 @@
         checkStatement squealRendering stmt
 
       -- Postfix Operators
-      it "select * from users where bio is null" $ do
+      it "select * from users where users.bio is null" $ do
         let
           stmt
             :: Statement
@@ -1119,7 +1244,7 @@
           squealRendering = "SELECT * FROM \"users\" AS \"users\" WHERE \"users\".\"bio\" IS NULL"
         checkStatement squealRendering stmt
 
-      it "select * from users where bio is not null" $ do
+      it "select * from users where users.bio is not null" $ do
         let
           stmt
             :: Statement
@@ -1135,7 +1260,7 @@
 
       describe "function calls" $ do
         -- Function Calls
-        it "select coalesce(bio, 'no bio') as bio from users" $ do
+        it "select coalesce(users.bio, 'no bio') as bio from users" $ do
           let
             stmt :: Statement DB () (Field "bio" Text, ())
             stmt = [ssql| select coalesce(users.bio, 'no bio') as bio from users |]
@@ -1144,7 +1269,7 @@
               "SELECT COALESCE(\"users\".\"bio\", (E'no bio' :: text)) AS \"bio\" FROM \"users\" AS \"users\""
           checkStatement squealRendering stmt
 
-        it "select lower(name) as lower_name from users" $ do
+        it "select lower(users.name) as lower_name from users" $ do
           let
             stmt :: Statement DB () (Field "lower_name" Text, ())
             stmt = [ssql| select lower(users.name) as lower_name from users |]
@@ -1153,7 +1278,7 @@
               "SELECT lower(\"users\".\"name\") AS \"lower_name\" FROM \"users\" AS \"users\""
           checkStatement squealRendering stmt
 
-        it "select char_length(name) as name_len from users" $ do
+        it "select char_length(users.name) as name_len from users" $ do
           let
             stmt :: Statement DB () (Field "name_len" Int32, ())
             stmt = [ssql| select char_length(users.name) as name_len from users |]
@@ -1162,7 +1287,7 @@
               "SELECT char_length(\"users\".\"name\") AS \"name_len\" FROM \"users\" AS \"users\""
           checkStatement squealRendering stmt
 
-        it "select character_length(name) as name_len_alias from users" $ do
+        it "select character_length(users.name) as name_len_alias from users" $ do
           let
             stmt :: Statement DB () (Field "name_len_alias" Int32, ())
             stmt = [ssql| select character_length(users.name) as name_len_alias from users |]
@@ -1171,7 +1296,7 @@
               "SELECT char_length(\"users\".\"name\") AS \"name_len_alias\" FROM \"users\" AS \"users\""
           checkStatement squealRendering stmt
 
-        it "select upper(name) as upper_name from users" $ do
+        it "select \"upper\"(users.name) as upper_name from users" $ do
           let
             stmt :: Statement DB () (Field "upper_name" Text, ())
             stmt = [ssql| select "upper"(users.name) as upper_name from users |]
@@ -1225,7 +1350,7 @@
           checkStatement squealRendering2 (mkStatement "Bob")
 
       -- PARENS (implicitly tested by complex expressions)
-      it "select (id + 1) * 2 as calc from emails" $ do
+      it "select (emails.id + 1) * 2 as calc from emails" $ do
         let
           stmt :: Statement DB () (Field "calc" Int32, ())
           stmt = [ssql| select (emails.id + 1) * 2 as calc from emails |]
@@ -1235,7 +1360,7 @@
         checkStatement squealRendering stmt
 
       -- IN / NOT IN
-      it "select * from users where name in ('Alice', 'Bob')" $ do
+      it "select * from users where users.name in ('Alice', 'Bob')" $ do
         let
           stmt
             :: Statement
@@ -1250,7 +1375,7 @@
             "SELECT * FROM \"users\" AS \"users\" WHERE \"users\".\"name\" IN ((E'Alice' :: text), (E'Bob' :: text))"
         checkStatement squealRendering stmt
 
-      it "select * from users where name not in ('Alice', 'Bob')" $ do
+      it "select * from users where users.name not in ('Alice', 'Bob')" $ do
         let
           stmt
             :: Statement
@@ -1266,7 +1391,7 @@
         checkStatement squealRendering stmt
 
       -- BETWEEN / NOT BETWEEN
-      it "select * from emails where id between 0 and 10" $ do
+      it "select * from emails where emails.id between 0 and 10" $ do
         let
           stmt
             :: Statement
@@ -1279,7 +1404,7 @@
             "SELECT * FROM \"emails\" AS \"emails\" WHERE \"emails\".\"id\" BETWEEN 0 AND 10"
         checkStatement squealRendering stmt
 
-      it "select * from emails where id not between 0 and 10" $ do
+      it "select * from emails where emails.id not between 0 and 10" $ do
         let
           stmt
             :: Statement
@@ -1293,7 +1418,7 @@
         checkStatement squealRendering stmt
 
       -- CAST
-      it "select cast(e.id as text) as casted_id from emails as e" $ do
+      it "select (e.id :: text) as casted_id from emails as e" $ do
         let
           stmt :: Statement DB () (Field "casted_id" Text, ())
           stmt = [ssql| select (e.id :: text) as casted_id from emails as e |]
