🐿️ Type safe SQL in Gleam
73

Configure Feed

Select the types of activity you want to include in your feed.

update mug dependency

Giacomo Cavalieri (Jul 4, 2025, 7:05 PM +0200) ff1c21d0 4488afb3

+76 -72
+3
CHANGELOG.md
··· 5 5 - Updated code for `gleam_stdlib` `>= 0.61.0 and < 1.0.0`. 6 6 ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 7 7 8 + - Updated the `mug` dependency to `>= 3.0.0 and < 4.0.0`. 9 + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 10 + 8 11 ## v3.0.6 - 2025-06-24 9 12 10 13 - Updated the `mug` dependency to `>= 2.0.0 and < 3.0.0`
+1 -1
gleam.toml
··· 17 17 gleam_regexp = ">= 1.0.0 and < 2.0.0" 18 18 gleam_stdlib = ">= 0.51.0 and < 2.0.0" 19 19 justin = ">= 1.0.1 and < 2.0.0" 20 - mug = ">= 2.0.0 and < 3.0.0" 21 20 non_empty_list = ">= 2.1.0 and < 3.0.0" 22 21 pog = ">= 3.1.0 and < 4.0.0" 23 22 simplifile = ">= 2.0.1 and < 3.0.0" ··· 26 25 tote = ">= 1.0.2 and < 2.0.0" 27 26 youid = ">= 1.2.0 and < 2.0.0" 28 27 glexer = ">= 2.1.0 and < 3.0.0" 28 + mug = ">= 3.0.0 and < 4.0.0" 29 29 30 30 [dev-dependencies] 31 31 birdie = ">= 1.1.8 and < 2.0.0"
+2 -2
manifest.toml
··· 23 23 { name = "gleeunit", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "63022D81C12C17B7F1A60E029964E830A4CBD846BBC6740004FC1F1031AE0326" }, 24 24 { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, 25 25 { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 26 - { name = "mug", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "197E0BBC57901AC2A418E6175079EBF734917EB799C53B847E208CF2A11E9136" }, 26 + { name = "mug", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "49AD2B71690C6A615453D272300951C4BDE19FBF55B167D9C951F5CD89FEC820" }, 27 27 { name = "non_empty_list", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "non_empty_list", source = "hex", outer_checksum = "3BF1496B475F3F2CE2EA18157587329812493369B2A7C5B9DCFEA5C007670A3B" }, 28 28 { name = "opentelemetry_api", version = "1.4.0", build_tools = ["rebar3", "mix"], requirements = [], otp_app = "opentelemetry_api", source = "hex", outer_checksum = "3DFBBFAA2C2ED3121C5C483162836C4F9027DEF469C41578AF5EF32589FCFC58" }, 29 29 { name = "pg_types", version = "0.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "pg_types", source = "hex", outer_checksum = "B02EFA785CAECECF9702C681C80A9CA12A39F9161A846CE17B01FB20AEEED7EB" }, ··· 56 56 gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 57 57 glexer = { version = ">= 2.1.0 and < 3.0.0" } 58 58 justin = { version = ">= 1.0.1 and < 2.0.0" } 59 - mug = { version = ">= 2.0.0 and < 3.0.0" } 59 + mug = { version = ">= 3.0.0 and < 4.0.0" } 60 60 non_empty_list = { version = ">= 2.1.0 and < 3.0.0" } 61 61 pog = { version = ">= 3.1.0 and < 4.0.0" } 62 62 shellout = { version = ">= 1.6.0 and < 2.0.0" }
+13 -11
src/squirrel/internal/database/postgres.gleam
··· 305 305 queries: List(UntypedQuery), 306 306 connection: ConnectionOptions, 307 307 ) -> Result(#(List(TypedQuery), List(Error)), Error) { 308 + let ConnectionOptions(host:, port:, timeout:, user:, password:, database:) = 309 + connection 310 + 308 311 use db <- result.try( 309 - pg.connect(connection.host, connection.port, connection.timeout) 312 + pg.connect(host, port, timeout) 310 313 |> result.map_error(error.PgCannotEstablishTcpConnection( 311 - host: connection.host, 312 - port: connection.port, 314 + host: host, 315 + port: port, 313 316 reason: _, 314 317 )), 315 318 ) ··· 326 329 // 327 330 328 331 let setup_script = { 329 - use _ <- eval.try(authenticate(connection)) 332 + use _ <- eval.try(authenticate(user:, database:, password:)) 330 333 use _ <- eval.try(ensure_postgres_version()) 331 334 eval.return(Nil) 332 335 } ··· 341 344 |> Ok 342 345 } 343 346 344 - fn authenticate(connection: ConnectionOptions) -> Db(Nil) { 345 - let ConnectionOptions(user:, database:, password:, ..) = connection 346 - 347 + fn authenticate( 348 + user user: String, 349 + database database: String, 350 + password password: String, 351 + ) -> Db(Nil) { 347 352 let params = [#("user", user), #("database", database)] 348 353 use _ <- eval.try(send(pg.FeStartupMessage(params))) 349 354 ··· 381 386 // In case there's a receive error while waiting for the server to be ready 382 387 // we want to display a more helpful error message because the problem here 383 388 // must be with an invalid username/database combination. 384 - |> eval.replace_error(error.PgInvalidUserDatabase( 385 - user: connection.user, 386 - database: connection.database, 387 - )), 389 + |> eval.replace_error(error.PgInvalidUserDatabase(user:, database:)), 388 390 ) 389 391 390 392 eval.return(Nil)
+43 -55
src/squirrel/internal/database/postgres_protocol.gleam
··· 25 25 } 26 26 27 27 pub fn connect(host, port, timeout) { 28 - let options = mug.ConnectionOptions(host: host, port: port, timeout: timeout) 28 + let options = 29 + mug.ConnectionOptions( 30 + host:, 31 + port:, 32 + timeout:, 33 + ip_version_preference: mug.Ipv4Preferred, 34 + ) 29 35 use socket <- result.try(mug.connect(options)) 30 - Ok(Connection(socket: socket, buffer: <<>>, timeout: timeout)) 36 + Ok(Connection(socket:, buffer: <<>>, timeout:)) 31 37 } 32 38 33 39 pub type StateInitial { ··· 66 72 fn receive_startup_rec(conn: Connection, state: StateInitial) { 67 73 case receive(conn) { 68 74 Ok(#(conn, BeAuthenticationOk)) -> receive_startup_rec(conn, state) 69 - Ok(#(conn, BeParameterStatus(name: name, value: value))) -> 75 + Ok(#(conn, BeParameterStatus(name:, value:))) -> 70 76 receive_startup_rec( 71 77 conn, 72 78 StateInitial(parameters: dict.insert(state.parameters, name, value)), 73 79 ) 74 - Ok(#(conn, BeBackendKeyData(secret_key: secret_key, process_id: process_id))) -> 80 + Ok(#(conn, BeBackendKeyData(secret_key:, process_id:))) -> 75 81 receive_startup_1( 76 82 conn, 77 83 State( 78 84 parameters: state.parameters, 79 - secret_key: secret_key, 80 - process_id: process_id, 85 + secret_key:, 86 + process_id:, 81 87 oids: default_oids(), 82 88 ), 83 89 ) ··· 88 94 89 95 fn receive_startup_1(conn: Connection, state: State) { 90 96 case receive(conn) { 91 - Ok(#(conn, BeParameterStatus(name: name, value: value))) -> 97 + Ok(#(conn, BeParameterStatus(name:, value:))) -> 92 98 receive_startup_1( 93 99 conn, 94 100 State(..state, parameters: dict.insert(state.parameters, name, value)), ··· 449 455 BeAuthenticationOk -> encode("R", <<0:32>>) 450 456 BeAuthenticationKerberosV5 -> encode("R", <<2:32>>) 451 457 BeAuthenticationCleartextPassword -> encode("R", <<3:32>>) 452 - BeAuthenticationMD5Password(salt: salt) -> encode("R", <<5:32, salt:bits>>) 458 + BeAuthenticationMD5Password(salt:) -> encode("R", <<5:32, salt:bits>>) 453 459 BeAuthenticationGSS -> encode("R", <<7:32>>) 454 460 BeAuthenticationGSSContinue(data) -> encode("R", <<8:32, data:bits>>) 455 461 BeAuthenticationSSPI -> encode("R", <<9:32>>) 456 462 BeAuthenticationSASL(a) -> encode_authentication_sasl(a) 457 463 BeAuthenticationSASLContinue(data) -> encode("R", <<11:32, data:bits>>) 458 - BeAuthenticationSASLFinal(data: data) -> encode("R", <<12:32, data:bits>>) 464 + BeAuthenticationSASLFinal(data:) -> encode("R", <<12:32, data:bits>>) 459 465 BeBackendKeyData(pid, sk) -> encode("K", <<pid:32, sk:32>>) 460 466 BeBindComplete -> encode("2", <<>>) 461 467 BeCloseComplete -> encode("3", <<>>) ··· 481 487 pub fn encode_frontend_message(message: FrontendMessage) { 482 488 case message { 483 489 FeBind(a, b, c, d, e) -> encode_bind(a, b, c, d, e) 484 - FeCancelRequest(process_id: process_id, secret_key: secret_key) -> << 485 - 16:32, 486 - 1234:16, 487 - 5678:16, 488 - process_id:32, 489 - secret_key:32, 490 + FeCancelRequest(process_id:, secret_key:) -> << 491 + 16:32, 1234:16, 5678:16, process_id:32, secret_key:32, 490 492 >> 491 493 FeClose(what, name) -> 492 494 encode("C", <<wire_what(what):bits, encode_string(name):bits>>) ··· 534 536 pub const protocol_version_minor = <<0:16>> 535 537 536 538 pub const protocol_version = << 537 - protocol_version_major:bits, 538 - protocol_version_minor:bits, 539 + protocol_version_major:bits, protocol_version_minor:bits, 539 540 >> 540 541 541 542 fn encode_startup_message(params) { ··· 634 635 } 635 636 636 637 fn with_buffer(conn: Connection, buffer: BitArray) { 637 - Connection(..conn, buffer: buffer) 638 + Connection(..conn, buffer:) 638 639 } 639 640 640 641 // decode a single message from the packet ··· 667 668 <<"R":utf8, 0:32>> -> Ok(BeAuthenticationOk) 668 669 <<"R":utf8, 2:32>> -> Ok(BeAuthenticationKerberosV5) 669 670 <<"R":utf8, 3:32>> -> Ok(BeAuthenticationCleartextPassword) 670 - <<"R":utf8, 5:32, salt:bytes>> -> 671 - Ok(BeAuthenticationMD5Password(salt: salt)) 671 + <<"R":utf8, 5:32, salt:bytes>> -> Ok(BeAuthenticationMD5Password(salt:)) 672 672 <<"R":utf8, 7:32>> -> Ok(BeAuthenticationGSS) 673 673 <<"R":utf8, 8:32, auth_data:bytes>> -> 674 - Ok(BeAuthenticationGSSContinue(auth_data: auth_data)) 674 + Ok(BeAuthenticationGSSContinue(auth_data:)) 675 675 <<"R":utf8, 9:32>> -> Ok(BeAuthenticationSSPI) 676 676 <<"R":utf8, 10:32, data:bytes>> -> decode_authentication_sasl(data) 677 - <<"R":utf8, 11:32, data:bytes>> -> 678 - Ok(BeAuthenticationSASLContinue(data: data)) 679 - <<"R":utf8, 12:32, data:bytes>> -> Ok(BeAuthenticationSASLFinal(data: data)) 677 + <<"R":utf8, 11:32, data:bytes>> -> Ok(BeAuthenticationSASLContinue(data:)) 678 + <<"R":utf8, 12:32, data:bytes>> -> Ok(BeAuthenticationSASLFinal(data:)) 680 679 <<"K":utf8, pid:32, sk:32>> -> 681 680 Ok(BeBackendKeyData(process_id: pid, secret_key: sk)) 682 681 <<"2":utf8>> -> Ok(BeBindComplete) ··· 719 718 ) -> Result(#(FrontendMessage, BitArray), MessageDecodingError) { 720 719 case packet { 721 720 <<16:32, 1234:16, 5678:16, process_id:32, secret_key:32, next:bytes>> -> 722 - Ok(#( 723 - FeCancelRequest(process_id: process_id, secret_key: secret_key), 724 - next, 725 - )) 721 + Ok(#(FeCancelRequest(process_id:, secret_key:), next)) 726 722 <<8:32, 1234:16, 5679:16, next:bytes>> -> Ok(#(FeSslRequest, next)) 727 723 <<8:32, 1234:16, 5680:16, next:bytes>> -> Ok(#(FeGssEncRequest, next)) 728 724 // not sure if there's a way to use the `protocol_version` constant here ··· 800 796 use #(name, binary) <- try(decode_string(binary)) 801 797 use #(query, binary) <- try(decode_string(binary)) 802 798 use parameter_object_ids <- try(decode_parameter_object_ids(binary)) 803 - Ok(FeParse( 804 - name: name, 805 - query: query, 806 - parameter_object_ids: parameter_object_ids, 807 - )) 799 + Ok(FeParse(name:, query:, parameter_object_ids:)) 808 800 } 809 801 810 802 fn decode_parameter_object_ids(binary) { ··· 832 824 case rest { 833 825 <<>> -> 834 826 Ok(FeFunctionCall( 835 - argument_format: argument_format, 836 - arguments: arguments, 837 - object_id: object_id, 838 - result_format: result_format, 827 + argument_format:, 828 + arguments:, 829 + object_id:, 830 + result_format:, 839 831 )) 840 832 _ -> dec_err("invalid function call, data remains", rest) 841 833 } ··· 890 882 case binary { 891 883 <<>> -> 892 884 Ok(FeBind( 893 - portal: portal, 894 - statement_name: statement_name, 895 - parameter_format: parameter_format, 896 - parameters: parameters, 897 - result_format: result_format, 885 + portal:, 886 + statement_name:, 887 + parameter_format:, 888 + parameters:, 889 + result_format:, 898 890 )) 899 891 _ -> dec_err("Bind message too long", binary) 900 892 } ··· 1060 1052 use strings <- try(read_strings(binary, 2, [])) 1061 1053 case strings { 1062 1054 [channel, payload] -> 1063 - Ok(BeNotificationResponse( 1064 - process_id: process_id, 1065 - channel: channel, 1066 - payload: payload, 1067 - )) 1055 + Ok(BeNotificationResponse(process_id:, channel:, payload:)) 1068 1056 _ -> dec_err("invalid notification response encoding", binary) 1069 1057 } 1070 1058 } ··· 1082 1070 fn decode_parameter_status(binary) { 1083 1071 use strings <- try(decode_strings(binary)) 1084 1072 case strings { 1085 - [name, value] -> Ok(BeParameterStatus(name: name, value: value)) 1073 + [name, value] -> Ok(BeParameterStatus(name:, value:)) 1086 1074 _ -> dec_err("invalid parameter status", binary) 1087 1075 } 1088 1076 } ··· 1238 1226 )) -> 1239 1227 Ok(#( 1240 1228 RowDescriptionField( 1241 - name: name, 1242 - table_oid: table_oid, 1243 - attr_number: attr_number, 1244 - data_type_oid: data_type_oid, 1245 - data_type_size: data_type_size, 1246 - type_modifier: type_modifier, 1247 - format_code: format_code, 1229 + name:, 1230 + table_oid:, 1231 + attr_number:, 1232 + data_type_oid:, 1233 + data_type_size:, 1234 + type_modifier:, 1235 + format_code:, 1248 1236 ), 1249 1237 tail, 1250 1238 ))
+14 -3
src/squirrel/internal/error.gleam
··· 15 15 // --- POSTGRES RELATED ERRORS ----------------------------------------------- 16 16 /// When we can't establish a TCP connection to the server. 17 17 /// 18 - PgCannotEstablishTcpConnection(host: String, port: Int, reason: mug.Error) 18 + PgCannotEstablishTcpConnection( 19 + host: String, 20 + port: Int, 21 + reason: mug.ConnectError, 22 + ) 19 23 20 24 /// When the server immediately closes the connection right after we try to 21 25 /// connect using the given username and database. ··· 245 249 // that is actually printed and we do not have to make any effort to add and 246 250 // print new errors. 247 251 let printable_error = case error { 248 - PgCannotEstablishTcpConnection(host:, port:, reason:) -> 252 + PgCannotEstablishTcpConnection(host:, port:, reason:) -> { 253 + let mug_error = case reason { 254 + mug.ConnectFailedBoth(ipv4: error, ipv6: _) 255 + | mug.ConnectFailedIpv4(ipv4: error) 256 + | mug.ConnectFailedIpv6(ipv6: error) -> error 257 + } 258 + 249 259 printable_error("Cannot establish TCP connection") 250 - |> add_paragraph(case reason { 260 + |> add_paragraph(case mug_error { 251 261 mug.Econnrefused -> 252 262 "I couldn't connect to the database because " 253 263 <> style_inline_code(host) ··· 280 290 " <> style_inline_code(host) <> " at port " <> int.to_string(port) <> ": 281 291 " <> string.inspect(reason) 282 292 }) 293 + } 283 294 284 295 PgInvalidUserDatabase(user:, database:) -> 285 296 printable_error("Cannot connect")