···2233## Unreleased
4455+- Added support for the `name` type, represented as a Gleam `String`.
66+ ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
77+58## 4.5.0 - 2025-10-24
69710- Squirrel will no longer perform code generation if there's any errors in the
···4747 name:,
4848 comment: [],
4949 content: "
5050+with recursive types as (
5151+ -- This selects the initial type, it might be an array!
5252+ select
5353+ pg_type.oid as oid,
5454+ pg_type.typname as name,
5555+ pg_type.typelem as elem,
5656+ pg_type.typtype as kind,
5757+ 0 as jumps
5858+ from pg_type
5959+ where pg_type.oid = $1
6060+ union all
6161+ -- So we keep selecting the type contained in it recursively until we
6262+ -- reach a base type (where the `elem` field is 0 and can't be joined with
6363+ -- any other type).
6464+ select
6565+ pg_type.oid as oid,
6666+ pg_type.typname as name,
6767+ pg_type.typelem as elem,
6868+ pg_type.typtype as kind,
6969+ types.jumps + 1 as jumps
7070+ from pg_type
7171+ join types
7272+ on pg_type.oid = types.elem
7373+ -- We need to special case the built-in `name` type: for some reason
7474+ -- that's treated as a char array, so we have to stop the recursion
7575+ -- earlier
7676+ and types.name != 'name'
7777+)
7878+-- Finally we only get the last base type (keeping track of how many jumps we
7979+-- did to properly wrap it in an array type the correct number of times)
5080select
5151- -- The name of the type or, if the type is an array, the name of its
5252- -- elements' type.
5353- case
5454- when elem.typname is null then type.typname
5555- else elem.typname
5656- end as type,
5757-5858- -- The oid of the type or the array item type.
5959- case
6060- when elem.typname is null then type.oid
6161- else elem.oid
6262- end as oid,
6363-6464- -- Tells us how to interpret the first column: if this is true then the first
6565- -- column is the type of the elements of the array type.
6666- -- Otherwise it means we've found a base type.
6767- case
6868- when elem.typname is null then false
6969- else true
7070- end as is_array,
7171-7272- -- The type of the type/array item.
7373- -- It will be 'e' if the thing is an enum.
7474- case
7575- when elem.typname is null then type.typtype
7676- else elem.typtype
7777- end as kind
7878-from
7979- pg_type as type
8080- left join pg_type as elem on type.typelem = elem.oid
8181-where
8282- type.oid = $1
8181+ types.oid,
8282+ types.name,
8383+ types.kind,
8484+ types.jumps
8585+from types
8686+order by types.jumps desc
8787+limit 1
8388",
8489 )
8590}
···9398 name:,
9499 comment: [],
95100 content: "
9696-select
9797- enumlabel
9898-from
9999- pg_enum
100100-where
101101- enumtypid = $1
102102-order by
103103- enumsortorder asc
101101+select enumlabel
102102+from pg_enum
103103+where enumtypid = $1
104104+order by enumsortorder asc
104105",
105106 )
106107}
···117118select
118119 -- Whether the column has a not-null constraint.
119120 attnotnull
120120-from
121121- pg_attribute
121121+from pg_attribute
122122where
123123 -- The oid of the table the column comes from.
124124 attrelid = $1
···152152 ///
153153 PBase(name: String)
154154155155- /// An array type like `int[]`, `text[]`, ...
156156- ///
157157- PArray(inner: PgType)
158158-159155 /// An enum, for example:
160156 ///
161157 /// ```sql
···168164 ///
169165 ///
170166 PEnum(name: String, variants: List(String))
171171-172172- /// A type that could also be `NULL`, this is particularly common for columns
173173- /// that do not have a `not null` constraint; or for those coming from partial
174174- /// joins.
175175- ///
176176- POption(inner: PgType)
177167}
178168179169/// The context in which all database-related actions will take place.
···261251fn pg_to_gleam_type(
262252 query: UntypedQuery,
263253 type_: PgType,
254254+ // How many times the base type needs to be wrapped in a list type. For
255255+ // example if we get 2, that means we're dealing with an array of arrays in
256256+ // postgres (like `text[][]`).
257257+ list_wrappings: Int,
264258) -> Result(gleam.Type, Error) {
265259 case type_ {
266266- PArray(inner:) ->
267267- pg_to_gleam_type(query, inner)
268268- |> result.map(gleam.List)
269269-270270- POption(inner:) ->
271271- pg_to_gleam_type(query, inner)
272272- |> result.map(gleam.Option)
273273-274260 PBase(name:) ->
275261 case name {
276262 "bool" -> Ok(gleam.Bool)
277277- "text" | "char" | "bpchar" | "varchar" | "citext" -> Ok(gleam.String)
263263+ "text" | "char" | "bpchar" | "varchar" | "citext" | "name" ->
264264+ Ok(gleam.String)
278265 "float4" | "float8" -> Ok(gleam.Float)
279266 "numeric" -> Ok(gleam.Numeric)
280267 "int2" | "int4" | "int8" -> Ok(gleam.Int)
···290277 PEnum(name:, variants:) ->
291278 gleam.try_make_enum(name, variants)
292279 |> result.map_error(invalid_enum_error(query, name, _))
280280+ }
281281+ |> result.map(wrap_in_list(_, list_wrappings))
282282+}
283283+284284+/// Wraps a Gleam type in the list type the given number of times.
285285+/// For example `wrap_in_list(String, 2)` will become `List(List(String))`.
286286+///
287287+fn wrap_in_list(value: gleam.Type, times: Int) -> gleam.Type {
288288+ case times <= 0 {
289289+ True -> value
290290+ _ -> wrap_in_list(gleam.List(value), times - 1)
293291 }
294292}
295293···731729 // check wether it is an array or not and the type of the type / array item.
732730 // It's safe to assert because this query is hard coded in our code and the
733731 // output shape cannot change without us changing that query.
734734- let assert [[name, oid, is_array, kind]] = res
732732+ let assert [[oid, name, kind, list_wrappings]] = res
733733+ let assert <<oid:size(32)>> = oid
735734 let assert Ok(name) = bit_array.to_string(name)
736735 let assert Ok(kind) = bit_array.to_string(kind)
737737- let assert <<oid:size(32)>> = oid
736736+ let assert <<list_wrappings:size(32)>> = list_wrappings
738737739738 use type_ <- eval.try(case kind {
740739 "e" -> resolve_enum_type(name, oid)
741740 _ -> eval.return(PBase(name))
742741 })
743742744744- // We then decode the bitarrays we got as a result:
745745- // - `name` is just a string
746746- // - `is_array` is a pg boolean
747747- //
748748- let type_ = case bit_array_to_bool(is_array) {
749749- True -> PArray(type_)
750750- False -> type_
751751- }
752752-753753- pg_to_gleam_type(query, type_)
743743+ pg_to_gleam_type(query, type_, list_wrappings)
754744 |> eval.from_result
755745}
756746
···578578 |> birdie.snap(title: "enum array decoding")
579579}
580580581581+// https://github.com/giacomocavalieri/squirrel/issues/119
582582+pub fn name_encoding_test() {
583583+ "select 1 as res where $1 = 'name'::name"
584584+ |> should_codegen
585585+ |> birdie.snap(title: "name encoding")
586586+}
587587+588588+pub fn name_decoding_test() {
589589+ "select 'name'::name as res"
590590+ |> should_codegen
591591+ |> birdie.snap(title: "name decoding")
592592+}
593593+594594+pub fn array_of_names_encoding_test() {
595595+ "select 1 as res where $1 = '{}'::name[]"
596596+ |> should_codegen
597597+ |> birdie.snap(title: "array of names encoding")
598598+}
599599+600600+pub fn array_of_names_decoding_test() {
601601+ "select '{}'::name[] as res"
602602+ |> should_codegen
603603+ |> birdie.snap(title: "array of names decoding")
604604+}
605605+581606// --- CODEGEN STRUCTURE TESTS -------------------------------------------------
582607// This is a group of tests to ensure the generated code has some specific
583608// structure (e.g. the names and comments are what we expect...)