From d6c17facac3b2eab320795481d76c9ee4af13a42 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Sat, 15 Nov 2025 11:13:10 +0100 Subject: [PATCH 1/2] make sure we use a single connection for the tests --- gleam.toml | 1 + manifest.toml | 2 + src/squirrel.gleam | 68 +++++++++---------- src/squirrel/internal/database/postgres.gleam | 29 ++++---- test/squirrel_test.gleam | 30 +++++--- 5 files changed, 73 insertions(+), 57 deletions(-) diff --git a/gleam.toml b/gleam.toml index f11e2d5..7200904 100644 --- a/gleam.toml +++ b/gleam.toml @@ -34,3 +34,4 @@ gleam_erlang = ">= 1.0.0 and < 2.0.0" gleeunit = ">= 1.0.0 and < 2.0.0" shellout = ">= 1.6.0 and < 2.0.0" temporary = ">= 1.0.0 and < 2.0.0" +global_value = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index 952e617..14ad4a1 100644 --- a/manifest.toml +++ b/manifest.toml @@ -23,6 +23,7 @@ packages = [ { name = "gleam_time", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" }, { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, + { name = "global_value", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "global_value", source = "hex", outer_checksum = "23F74C91A7B819C43ABCCBF49DAD5BB8799D81F2A3736BA9A534BD47F309FF4F" }, { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, { name = "mug", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "49AD2B71690C6A615453D272300951C4BDE19FBF55B167D9C951F5CD89FEC820" }, { name = "non_empty_list", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "non_empty_list", source = "hex", outer_checksum = "3BF1496B475F3F2CE2EA18157587329812493369B2A7C5B9DCFEA5C007670A3B" }, @@ -57,6 +58,7 @@ gleam_stdlib = { version = ">= 0.51.0 and < 2.0.0" } gleam_time = { version = ">= 1.2.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } glexer = { version = ">= 2.1.0 and < 3.0.0" } +global_value = { version = ">= 1.0.0 and < 2.0.0" } justin = { version = ">= 1.0.1 and < 2.0.0" } mug = { version = ">= 3.0.0 and < 4.0.0" } non_empty_list = { version = ">= 2.1.0 and < 3.0.0" } diff --git a/src/squirrel.gleam b/src/squirrel.gleam index ab3c8a3..2c7ee86 100644 --- a/src/squirrel.gleam +++ b/src/squirrel.gleam @@ -63,8 +63,8 @@ const squirrel_version = "v4.5.0" /// > that dependency to your project. /// pub fn main() { - case parse_cli_args(), connection_options() { - Error(Nil), _ -> { + case parse_cli_args() { + Error(Nil) -> { help_text() |> doc.to_string(term_width()) |> io.println @@ -72,43 +72,43 @@ pub fn main() { exit(1) } - // In case we cannot read the connection options we just immediately fail. - _, Error(error) -> { - error.to_doc(error) - |> doc.to_string(term_width()) - |> io.println + Ok(mode) -> + case result.try(connection_options(), postgres.connect_and_authenticate) { + Error(error) -> { + error.to_doc(error) + |> doc.to_string(term_width()) + |> io.println - exit(1) - } + exit(1) + } - // Otherwise we can walk through the file system and type all the queries, - // connecting to the database. - Ok(mode), Ok(options) -> { - let generated_queries = - walk(project.src()) - |> dict.merge(walk(project.test_())) - |> dict.merge(walk(project.dev())) - |> generate_queries(options) - - let #(report, status_code) = case mode { - GenerateCode -> - case ensure_no_errors(generated_queries) { - Error(errors) -> report_errors(errors) - Ok(generated_queries) -> + Ok(connection) -> { + let generated_queries = + walk(project.src()) + |> dict.merge(walk(project.test_())) + |> dict.merge(walk(project.dev())) + |> generate_queries(connection) + + let #(report, status_code) = case mode { + GenerateCode -> + case ensure_no_errors(generated_queries) { + Error(errors) -> report_errors(errors) + Ok(generated_queries) -> + generated_queries + |> write_queries + |> report_written_queries + } + + CheckGeneratedCode -> generated_queries - |> write_queries - |> report_written_queries + |> check_queries + |> report_checked_queries } - CheckGeneratedCode -> - generated_queries - |> check_queries - |> report_checked_queries + io.println(report) + exit(status_code) + } } - - io.println(report) - exit(status_code) - } } } @@ -340,7 +340,7 @@ fn walk(from: String) -> Dict(String, List(String)) { /// fn generate_queries( directories: Dict(String, List(String)), - connection: postgres.ConnectionOptions, + connection: postgres.Context, ) -> Dict(String, #(List(TypedQuery), List(Error))) { use _directory, files <- dict.map_values(directories) diff --git a/src/squirrel/internal/database/postgres.gleam b/src/squirrel/internal/database/postgres.gleam index e90cd93..5daeb7d 100644 --- a/src/squirrel/internal/database/postgres.gleam +++ b/src/squirrel/internal/database/postgres.gleam @@ -178,7 +178,7 @@ type PgType { /// The context in which all database-related actions will take place. /// -type Context { +pub opaque type Context { Context( /// A connection to the database. Squirrel does nothing fancy and just uses /// a single connection to run all the queries. @@ -295,18 +295,9 @@ fn pg_to_gleam_type( // --- CLI ENTRY POINT --------------------------------------------------------- -/// Connects to a Postgres database (using the given options) and types a list -/// of queries. -/// -/// This might fail with an `Error` if a database connection cannot be -/// established, making it impossible to type any of the queries. -/// Otherwise, it will try typing all the queries, retuning a list of typed ones -/// and a list of possible errors for the ones it couldn't type. -/// -pub fn main( - queries: List(UntypedQuery), +pub fn connect_and_authenticate( connection: ConnectionOptions, -) -> Result(#(List(TypedQuery), List(Error)), Error) { +) -> Result(Context, Error) { let ConnectionOptions( host:, port:, @@ -344,7 +335,21 @@ pub fn main( let #(context, connection) = eval.step(setup_script, context) use _ <- result.try(connection) + Ok(context) +} +/// Connects to a Postgres database (using the given options) and types a list +/// of queries. +/// +/// This might fail with an `Error` if a database connection cannot be +/// established, making it impossible to type any of the queries. +/// Otherwise, it will try typing all the queries, retuning a list of typed ones +/// and a list of possible errors for the ones it couldn't type. +/// +pub fn main( + queries: List(UntypedQuery), + context: Context, +) -> Result(#(List(TypedQuery), List(Error)), Error) { // After successfully authenticating we can try and type all the queries. list.map(queries, infer_types) |> eval_extra.run_all(context) diff --git a/test/squirrel_test.gleam b/test/squirrel_test.gleam index 311e8ee..bd093d4 100644 --- a/test/squirrel_test.gleam +++ b/test/squirrel_test.gleam @@ -5,6 +5,7 @@ import gleam/erlang/process import gleam/list import gleam/string import gleeunit +import global_value import pog import simplifile import squirrel @@ -260,6 +261,23 @@ type CodegenOptions { ) } +fn database_connection() { + global_value.create_with_unique_name("squirrel_test.global.database", fn() { + let assert Ok(connection) = + postgres.connect_and_authenticate(postgres.ConnectionOptions( + host: host, + port: port, + user: user, + database: database, + password: "", + timeout_seconds: 1, + )) + as "cannot connect to test database" + + connection + }) +} + fn type_queries( queries: List(#(String, String)), ) -> Result(#(List(TypedQuery), List(Error)), Error) { @@ -282,17 +300,7 @@ fn type_queries( } // We can then ask squirrel to type check all the queries. - postgres.main( - queries, - postgres.ConnectionOptions( - host: host, - port: port, - user: user, - database: database, - password: "", - timeout_seconds: 1, - ), - ) + postgres.main(queries, database_connection()) } result From fe9868b5af7fc4cae69806f449e9de81a19ae4d0 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Sat, 15 Nov 2025 11:13:10 +0100 Subject: [PATCH 2/2] add support for the 'name' type --- CHANGELOG.md | 3 + README.md | 28 ++-- .../array_of_names_decoding.accepted | 28 ++++ .../array_of_names_encoding.accepted | 30 ++++ birdie_snapshots/name_decoding.accepted | 28 ++++ birdie_snapshots/name_encoding.accepted | 30 ++++ src/squirrel/internal/database/postgres.gleam | 136 ++++++++---------- test/integration_test.gleam | 12 +- test/squirrel_test.gleam | 25 ++++ 9 files changed, 227 insertions(+), 93 deletions(-) create mode 100644 birdie_snapshots/array_of_names_decoding.accepted create mode 100644 birdie_snapshots/array_of_names_encoding.accepted create mode 100644 birdie_snapshots/name_decoding.accepted create mode 100644 birdie_snapshots/name_encoding.accepted diff --git a/CHANGELOG.md b/CHANGELOG.md index bf89bfb..f6a8e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Added support for the `name` type, represented as a Gleam `String`. + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) + ## 4.5.0 - 2025-10-24 - Squirrel will no longer perform code generation if there's any errors in the diff --git a/README.md b/README.md index e554686..cdd1677 100644 --- a/README.md +++ b/README.md @@ -191,20 +191,20 @@ This is needed in two places: The types that are currently supported are: -| postgres type | encoded as | decoded as | -| ------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -| `bool` | `Bool` | `Bool` | -| `text`, `char`, `bpchar`, `varchar`, `citext` | `String` | `String` | -| `float4`, `float8`, `numeric` | `Float` | `Float` | -| `int2`, `int4`, `int8` | `Int` | `Int` | -| `json`, `jsonb` | [`json.Json`](https://hexdocs.pm/gleam_json/gleam/json.html#Json) | `String` | -| `uuid` | [`uuid.Uuid`](https://hexdocs.pm/youid/youid/uuid.html#Uuid) | [`uuid.Uuid`](https://hexdocs.pm/youid/youid/uuid.html#Uuid) | -| `bytea` | `BitArray` | `BitArray` | -| `date` | [`calendar.Date`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#Date) | [`calendar.Date`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#Date) | -| `time` | [`calendar.TimeOfDay`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#TimeOfDay) | [`calendar.TimeOfDay`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#TimeOfDay) | -| `timestamp` | [`timestamp.Timestamp`](https://hexdocs.pm/gleam_time/gleam/time/timestamp.html#Timestamp) | [`timestamp.Timestamp`](https://hexdocs.pm/gleam_time/gleam/time/timestamp.html#Timestamp) | -| `[]` (where `` is any supported type) | `List()` | `List()` | -| user-defined enum | [Gleam custom type](https://tour.gleam.run/data-types/custom-types/) | [Gleam custom type](https://tour.gleam.run/data-types/custom-types/) | +| postgres type | encoded as | decoded as | +| ----------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| `bool` | `Bool` | `Bool` | +| `text`, `char`, `bpchar`, `varchar`, `citext`, `name` | `String` | `String` | +| `float4`, `float8`, `numeric` | `Float` | `Float` | +| `int2`, `int4`, `int8` | `Int` | `Int` | +| `json`, `jsonb` | [`json.Json`](https://hexdocs.pm/gleam_json/gleam/json.html#Json) | `String` | +| `uuid` | [`uuid.Uuid`](https://hexdocs.pm/youid/youid/uuid.html#Uuid) | [`uuid.Uuid`](https://hexdocs.pm/youid/youid/uuid.html#Uuid) | +| `bytea` | `BitArray` | `BitArray` | +| `date` | [`calendar.Date`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#Date) | [`calendar.Date`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#Date) | +| `time` | [`calendar.TimeOfDay`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#TimeOfDay) | [`calendar.TimeOfDay`](https://hexdocs.pm/gleam_time/gleam/time/calendar.html#TimeOfDay) | +| `timestamp` | [`timestamp.Timestamp`](https://hexdocs.pm/gleam_time/gleam/time/timestamp.html#Timestamp) | [`timestamp.Timestamp`](https://hexdocs.pm/gleam_time/gleam/time/timestamp.html#Timestamp) | +| `[]` (where `` is any supported type) | `List()` | `List()` | +| user-defined enum | [Gleam custom type](https://tour.gleam.run/data-types/custom-types/) | [Gleam custom type](https://tour.gleam.run/data-types/custom-types/) | ### Enums diff --git a/birdie_snapshots/array_of_names_decoding.accepted b/birdie_snapshots/array_of_names_decoding.accepted new file mode 100644 index 0000000..a26a88b --- /dev/null +++ b/birdie_snapshots/array_of_names_decoding.accepted @@ -0,0 +1,28 @@ +--- +version: 1.4.0 +title: array of names decoding +file: ./test/squirrel_test.gleam +test_name: array_of_names_decoding_test +--- + +import gleam/dynamic/decode +import pog + +pub type QueryRow { + QueryRow(res: List(String)) +} + +pub fn query( + db: pog.Connection, +) -> Result(pog.Returned(QueryRow), pog.QueryError) { + let decoder = { + use res <- decode.field(0, decode.list(decode.string)) + decode.success(QueryRow(res:)) + } + + "select '{}'::name[] as res" + |> pog.query + |> pog.returning(decoder) + |> pog.execute(db) +} + diff --git a/birdie_snapshots/array_of_names_encoding.accepted b/birdie_snapshots/array_of_names_encoding.accepted new file mode 100644 index 0000000..6f98199 --- /dev/null +++ b/birdie_snapshots/array_of_names_encoding.accepted @@ -0,0 +1,30 @@ +--- +version: 1.4.0 +title: array of names encoding +file: ./test/squirrel_test.gleam +test_name: array_of_names_encoding_test +--- + +import gleam/dynamic/decode +import pog + +pub type QueryRow { + QueryRow(res: Int) +} + +pub fn query( + db: pog.Connection, + arg_1: List(String), +) -> Result(pog.Returned(QueryRow), pog.QueryError) { + let decoder = { + use res <- decode.field(0, decode.int) + decode.success(QueryRow(res:)) + } + + "select 1 as res where $1 = '{}'::name[]" + |> pog.query + |> pog.parameter(pog.array(fn(value) { pog.text(value) }, arg_1)) + |> pog.returning(decoder) + |> pog.execute(db) +} + diff --git a/birdie_snapshots/name_decoding.accepted b/birdie_snapshots/name_decoding.accepted new file mode 100644 index 0000000..b06c705 --- /dev/null +++ b/birdie_snapshots/name_decoding.accepted @@ -0,0 +1,28 @@ +--- +version: 1.4.0 +title: name decoding +file: ./test/squirrel_test.gleam +test_name: name_decoding_test +--- + +import gleam/dynamic/decode +import pog + +pub type QueryRow { + QueryRow(res: String) +} + +pub fn query( + db: pog.Connection, +) -> Result(pog.Returned(QueryRow), pog.QueryError) { + let decoder = { + use res <- decode.field(0, decode.string) + decode.success(QueryRow(res:)) + } + + "select 'name'::name as res" + |> pog.query + |> pog.returning(decoder) + |> pog.execute(db) +} + diff --git a/birdie_snapshots/name_encoding.accepted b/birdie_snapshots/name_encoding.accepted new file mode 100644 index 0000000..59637b8 --- /dev/null +++ b/birdie_snapshots/name_encoding.accepted @@ -0,0 +1,30 @@ +--- +version: 1.4.0 +title: name encoding +file: ./test/squirrel_test.gleam +test_name: name_encoding_test +--- + +import gleam/dynamic/decode +import pog + +pub type QueryRow { + QueryRow(res: Int) +} + +pub fn query( + db: pog.Connection, + arg_1: String, +) -> Result(pog.Returned(QueryRow), pog.QueryError) { + let decoder = { + use res <- decode.field(0, decode.int) + decode.success(QueryRow(res:)) + } + + "select 1 as res where $1 = 'name'::name" + |> pog.query + |> pog.parameter(pog.text(arg_1)) + |> pog.returning(decoder) + |> pog.execute(db) +} + diff --git a/src/squirrel/internal/database/postgres.gleam b/src/squirrel/internal/database/postgres.gleam index 5daeb7d..ba65742 100644 --- a/src/squirrel/internal/database/postgres.gleam +++ b/src/squirrel/internal/database/postgres.gleam @@ -47,39 +47,44 @@ fn find_postgres_type_query() -> UntypedQuery { name:, comment: [], content: " +with recursive types as ( + -- This selects the initial type, it might be an array! + select + pg_type.oid as oid, + pg_type.typname as name, + pg_type.typelem as elem, + pg_type.typtype as kind, + 0 as jumps + from pg_type + where pg_type.oid = $1 + union all + -- So we keep selecting the type contained in it recursively until we + -- reach a base type (where the `elem` field is 0 and can't be joined with + -- any other type). + select + pg_type.oid as oid, + pg_type.typname as name, + pg_type.typelem as elem, + pg_type.typtype as kind, + types.jumps + 1 as jumps + from pg_type + join types + on pg_type.oid = types.elem + -- We need to special case the built-in `name` type: for some reason + -- that's treated as a char array, so we have to stop the recursion + -- earlier + and types.name != 'name' +) +-- Finally we only get the last base type (keeping track of how many jumps we +-- did to properly wrap it in an array type the correct number of times) select - -- The name of the type or, if the type is an array, the name of its - -- elements' type. - case - when elem.typname is null then type.typname - else elem.typname - end as type, - - -- The oid of the type or the array item type. - case - when elem.typname is null then type.oid - else elem.oid - end as oid, - - -- Tells us how to interpret the first column: if this is true then the first - -- column is the type of the elements of the array type. - -- Otherwise it means we've found a base type. - case - when elem.typname is null then false - else true - end as is_array, - - -- The type of the type/array item. - -- It will be 'e' if the thing is an enum. - case - when elem.typname is null then type.typtype - else elem.typtype - end as kind -from - pg_type as type - left join pg_type as elem on type.typelem = elem.oid -where - type.oid = $1 + types.oid, + types.name, + types.kind, + types.jumps +from types +order by types.jumps desc +limit 1 ", ) } @@ -93,14 +98,10 @@ fn find_enum_variants_query() -> UntypedQuery { name:, comment: [], content: " -select - enumlabel -from - pg_enum -where - enumtypid = $1 -order by - enumsortorder asc +select enumlabel +from pg_enum +where enumtypid = $1 +order by enumsortorder asc ", ) } @@ -117,8 +118,7 @@ fn find_column_nullability_query() -> UntypedQuery { select -- Whether the column has a not-null constraint. attnotnull -from - pg_attribute +from pg_attribute where -- The oid of the table the column comes from. attrelid = $1 @@ -152,10 +152,6 @@ type PgType { /// PBase(name: String) - /// An array type like `int[]`, `text[]`, ... - /// - PArray(inner: PgType) - /// An enum, for example: /// /// ```sql @@ -168,12 +164,6 @@ type PgType { /// /// PEnum(name: String, variants: List(String)) - - /// A type that could also be `NULL`, this is particularly common for columns - /// that do not have a `not null` constraint; or for those coming from partial - /// joins. - /// - POption(inner: PgType) } /// The context in which all database-related actions will take place. @@ -261,20 +251,17 @@ pub type ConnectionOptions { fn pg_to_gleam_type( query: UntypedQuery, type_: PgType, + // How many times the base type needs to be wrapped in a list type. For + // example if we get 2, that means we're dealing with an array of arrays in + // postgres (like `text[][]`). + list_wrappings: Int, ) -> Result(gleam.Type, Error) { case type_ { - PArray(inner:) -> - pg_to_gleam_type(query, inner) - |> result.map(gleam.List) - - POption(inner:) -> - pg_to_gleam_type(query, inner) - |> result.map(gleam.Option) - PBase(name:) -> case name { "bool" -> Ok(gleam.Bool) - "text" | "char" | "bpchar" | "varchar" | "citext" -> Ok(gleam.String) + "text" | "char" | "bpchar" | "varchar" | "citext" | "name" -> + Ok(gleam.String) "float4" | "float8" -> Ok(gleam.Float) "numeric" -> Ok(gleam.Numeric) "int2" | "int4" | "int8" -> Ok(gleam.Int) @@ -291,6 +278,17 @@ fn pg_to_gleam_type( gleam.try_make_enum(name, variants) |> result.map_error(invalid_enum_error(query, name, _)) } + |> result.map(wrap_in_list(_, list_wrappings)) +} + +/// Wraps a Gleam type in the list type the given number of times. +/// For example `wrap_in_list(String, 2)` will become `List(List(String))`. +/// +fn wrap_in_list(value: gleam.Type, times: Int) -> gleam.Type { + case times <= 0 { + True -> value + _ -> wrap_in_list(gleam.List(value), times - 1) + } } // --- CLI ENTRY POINT --------------------------------------------------------- @@ -731,26 +729,18 @@ fn find_gleam_type(query: UntypedQuery, oid: Int) -> Db(gleam.Type) { // check wether it is an array or not and the type of the type / array item. // It's safe to assert because this query is hard coded in our code and the // output shape cannot change without us changing that query. - let assert [[name, oid, is_array, kind]] = res + let assert [[oid, name, kind, list_wrappings]] = res + let assert <> = oid let assert Ok(name) = bit_array.to_string(name) let assert Ok(kind) = bit_array.to_string(kind) - let assert <> = oid + let assert <> = list_wrappings use type_ <- eval.try(case kind { "e" -> resolve_enum_type(name, oid) _ -> eval.return(PBase(name)) }) - // We then decode the bitarrays we got as a result: - // - `name` is just a string - // - `is_array` is a pg boolean - // - let type_ = case bit_array_to_bool(is_array) { - True -> PArray(type_) - False -> type_ - } - - pg_to_gleam_type(query, type_) + pg_to_gleam_type(query, type_, list_wrappings) |> eval.from_result } diff --git a/test/integration_test.gleam b/test/integration_test.gleam index d2deb7e..34294bd 100644 --- a/test/integration_test.gleam +++ b/test/integration_test.gleam @@ -61,6 +61,7 @@ const integration_tests = [ // Booleans TestType("bool", [TestValue("True"), TestValue("False")]), // Text data + TestType("name", [TestValue("\"hello\"")]), TestType("text", [TestValue("\"hello\"")]), TestType("char(1)", [TestValue("\"j\"")]), TestType("bpchar", [TestValue("\"j\"")]), @@ -360,11 +361,10 @@ fn test_project(dir: String) -> Result(String, #(Int, String)) { } fn safe_name(string: String) -> String { - let assert Ok(regex) = regexp.from_string("[()\\[\\]]") + let assert Ok(parens) = regexp.from_string("[()]") + let assert Ok(array) = regexp.from_string("\\[\\]") - let safe_string = regexp.replace(each: regex, with: "_", in: string) - case string.ends_with(string, "[]") { - False -> safe_string - True -> safe_string <> "array" - } + string + |> regexp.replace(each: parens, with: "_") + |> regexp.replace(each: array, with: "array") } diff --git a/test/squirrel_test.gleam b/test/squirrel_test.gleam index bd093d4..16a72a9 100644 --- a/test/squirrel_test.gleam +++ b/test/squirrel_test.gleam @@ -578,6 +578,31 @@ pub fn enum_array_decoding_test() { |> birdie.snap(title: "enum array decoding") } +// https://github.com/giacomocavalieri/squirrel/issues/119 +pub fn name_encoding_test() { + "select 1 as res where $1 = 'name'::name" + |> should_codegen + |> birdie.snap(title: "name encoding") +} + +pub fn name_decoding_test() { + "select 'name'::name as res" + |> should_codegen + |> birdie.snap(title: "name decoding") +} + +pub fn array_of_names_encoding_test() { + "select 1 as res where $1 = '{}'::name[]" + |> should_codegen + |> birdie.snap(title: "array of names encoding") +} + +pub fn array_of_names_decoding_test() { + "select '{}'::name[] as res" + |> should_codegen + |> birdie.snap(title: "array of names decoding") +} + // --- CODEGEN STRUCTURE TESTS ------------------------------------------------- // This is a group of tests to ensure the generated code has some specific // structure (e.g. the names and comments are what we expect...)