{"id":14966155,"url":"https://github.com/curttilmes/raku-dbpg","last_synced_at":"2025-10-25T13:31:17.822Z","repository":{"id":48977679,"uuid":"111245500","full_name":"CurtTilmes/raku-dbpg","owner":"CurtTilmes","description":"DB::Pg - PostgreSQL database interaction","archived":false,"fork":false,"pushed_at":"2022-08-14T12:31:38.000Z","size":203,"stargazers_count":12,"open_issues_count":5,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-31T07:21:24.423Z","etag":null,"topics":["perl6","postgresql"],"latest_commit_sha":null,"homepage":null,"language":"Raku","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CurtTilmes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-18T22:45:50.000Z","updated_at":"2024-12-05T13:41:13.000Z","dependencies_parsed_at":"2022-09-04T07:00:44.195Z","dependency_job_id":null,"html_url":"https://github.com/CurtTilmes/raku-dbpg","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CurtTilmes%2Fraku-dbpg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CurtTilmes%2Fraku-dbpg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CurtTilmes%2Fraku-dbpg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CurtTilmes%2Fraku-dbpg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CurtTilmes","download_url":"https://codeload.github.com/CurtTilmes/raku-dbpg/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238147594,"owners_count":19424291,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["perl6","postgresql"],"created_at":"2024-09-24T13:35:56.195Z","updated_at":"2025-10-25T13:31:12.463Z","avatar_url":"https://github.com/CurtTilmes.png","language":"Raku","funding_links":[],"categories":[],"sub_categories":[],"readme":"DB::Pg – PostgreSQL access for Perl 6\n=====================================\n\nThis is a reimplementation of Perl 6 bindings for PostgreSQL's\n[libpq](https://www.postgresql.org/docs/current/static/libpq.html).\n\nA very nice overview article is available in a blog post by Luca Ferrari: [A glance at Raku connectivity towards PostgreSQL](https://fluca1978.github.io/2021/03/29/RakuPostgreSQL.html).\n\nBasic usage\n-----------\n\n```perl6\nmy $pg = DB::Pg.new;  # You can pass in connection information if you want.\n```\n\nExecute a query, and get a single value:\n```perl6\nsay $pg.query('select 42').value;\n# 42\n```\n\nInsert some values using placeholders:\n```perl6\n$pg.query('insert into foo (x,y) values ($1,$2)', 1, 'this');\n```\n\nNote, placeholders use the `$1, $2, ...` syntax instead of `?` See\n[PREPARE](https://www.postgresql.org/docs/current/static/sql-prepare.html)\nfor more information.\n\nExecute a query returning a row as an array or hash;\n```perl6\nsay $pg.query('select * from foo where x = $1', 42).array;\nsay $pg.query('select * from foo where x = $1', 42).hash;\n```\n\nExecute a query returning a bunch of rows as arrays or hashes:\n```perl6\n.say for $pg.query('select * from foo').arrays;\n.say for $pg.query('select * from foo').hashes;\n```\n\nIf you have no placeholders/arguments and aren't retrieving\ninformation, you can use `execute`.  It does not `PREPARE` the query.\n\n```perl6\n$pg.execute('insert into foo (x,y) values (1,2)');\n```\n\nConnection Information\n----------------------\n\nThe most basic connection is just to pass in no options:\n\n```perl6\nmy $pg = DB::Pg.new;\n```\n\nThis is similar to just running `psql` on the command line.  It will\ntake advantage of any of the standard postgres environment variables.\n\nYou can also construct a standard [libpq connect\nstring](https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING). and pass it in.\n\nFor example:\n\n```perl6\nmy $pg = DB::Pg.new(conninfo =\u003e 'host=myhost port=5432 dbname=mydb');\n```\n\nYou could also pull those from environment variables with something like this:\n\n```perl6\nmy $conninfo = join \" \",\n        ('dbname=' ~ (%*ENV\u003cDB_NAME\u003e || die(\"missing DB_NAME in environment\"))),\n        (\"host=$_\" with %*ENV\u003cDB_HOST\u003e),\n        (\"user=$_\" with %*ENV\u003cDB_USER\u003e),\n        (\"password=$_\" with %*ENV\u003cDB_PASSWORD\u003e);\nmy $db = DB::Pg.new(:$conninfo);\n```\n\nBut you might as well use the standard `PG*` environment variables.\n\nYou can also just put all your connection information in a [Connection\nService\nFile](https://www.postgresql.org/docs/10/static/libpq-pgservice.html)\nand just use a conninfo string like \"service=foo\".\n\nThis is the same as just setting environment variable `PGSERVICE=foo`.\n\nConnection Caching\n------------------\n\nDatabase connection handles are created on demand, and cached for\nreuse in a connection pool.  Similarly, statement handles are\nprepared, cached and reused.\n\nWhen the first query is called, a new database connection will be\ncreated.  After the results are read from the connection, the\nconnection will be returned and cached in the pool.  When a later\nquery is performed, that cached connection will be reused.\n\nIf multiple simultaneous queries occur, perhaps in different threads,\neach will get a new connection so they won't interfere with one\nanother.\n\nFor example, you can perform database actions while iterating through\nresults from a query:\n\n```perl6\nfor $pg.query('select * from foo').hashes -\u003e %h\n{\n    $pg.query('update bar set ... where x = $1...$2...', %h\u003cx\u003e, %h\u003cy\u003e);\n}\n```\n\nYou can even do arbitrary queries in multiple threads without worrying\nabout connections:\n\n```perl6\nsay await do for ^10 {\n    start $pg.query('select $1::int, pg_sleep($1::float/10)', $_).value\n}\n```\n\nConnection caching is a nice convenience, but it does require some\ncare from the consumer.  If you call `query` with an imperative\nstatement (`insert`, `update`, `delete`) the connection will\nautomatically be returned to the pool for re-use.  For a query that\nreturns results, such as `select`, in order to reliably return the\nconnection to the pool for the next user, you must do one of two\nthings:\n\n1. Read all the results.  Once the last returned row is read, the\ndatabase connection handle will automatically get returned for reuse.\n\n2. Explicitly call `.finish` on the results object to prematurely return it.\n\nResults\n-------\n\nCalling `query` with a `select` or something that returns data, a\n`DB::Pg::Results` object will be returned.\n\nThe query results can be consumed from that object with the following\nmethods:\n\n* `.value` - a single scalar result\n* `.array` - a single array of results from one row\n* `.hash` - a single hash of results from one row\n* `.arrays` - a sequence of arrays of results from all rows\n* `.hashes` - a sequence of hashes of results from all rows\n\nYou can also query for some information about the results on the\nobject directly:\n\n* `.rows` - Total number of rows returned\n* `.columns` - List of column names returned\n* `.types` - List of Perl types of columns returned\n\nFor example:\n\n```perl6\nmy $results = $pg.query('select * from foo');\n\nsay $results.rows;\nsay $results.columns;\nsay $results.types;\n\n.say for $results.hashes;\n```\n\nIf the query isn't a select or otherwise doesn't return data, such as\nan INSERT, UPDATE, or DELETE, it will return the number of rows\naffected.\n\nDatabase\n--------\n\nThough you can just call `.query()` on the main `DB::Pg` object,\nsometimes you want to explicitly manage the database connection.  Use\nthe `.db` method to get a `DB::Pg::Database` object, and call\n`.finish` explicitly on it to return it to the pool when you are\nfinished with it.\n\nThe database object also has `.query()` and `.execute()` methods, they\njust don't automatically `.finish` to return the handle to the pool.\nYou must explicitly do that after use.\n\nThese are equivalent:\n\n```perl6\n.say for $pg.query('select * from foo').arrays;\n```\n\n```perl6\nmy $db = $pg.db;\n.say for $db.query('select * from foo').arrays;\n$db.finish;\n```\n\nThe database object also has some extra methods for separately\npreparing and executing a query:\n\n```perl6\nmy $db = $pg.db;\nmy $sth = $db.prepare('insert into foo (x,y) values ($1,$2)');\n$sth.execute(1, 'this');\n$sth.execute(2, 'that');\n$db.finish;\n```\n\n`.prepare()` returns a `DB::Pg::Statement` object.\n\nIt can be more efficient to perform many actions in this way and avoid\nthe overhead of returning the connection to the pool only to\nimmediately get it back again.\n\nTransactions\n------------\n\nThe database object can also manage transactions with the `.begin`,\n`.commit` and `.rollback` methods.\n\n```perl6\nmy $db = $pg.db;\nmy $sth = $db.prepare('insert into foo (x,y) values ($1,$2)');\n$db.begin;\n$sth.execute(1, 'this');\n$sth.execute(2, 'that');\n$db.commit;\n$db.finish;\n```\n\nThe `begin`/`commit` ensure that the statements between them happen\natomically, either all or none.\n\nTransactions can also dramatically improve performance for some\nactions, such as performing thousands of inserts/deletes/updates since\nthe indices for the affected table can be updated in bulk once for the\nentire transaction.\n\nIf you `.finish` the database prior to a `.commit`, an uncommitted\ntransaction will automatically be rolled back.\n\nAs a convenience, `.commit` also returns the database object, so you\ncan just `$db.commit.finish`.\n\nCursors\n-------\n\nWhen a query is performed, all the results from that query are\nimmediately returned from the server to the client.  For exceptionally\nlarge queries, this can be problematic, both waiting the time for the\nwhole query to execute, and the memory for all the\nresults. [Cursors](https://www.postgresql.org/docs/10/static/plpgsql-cursors.html)\nprovide a better way.\n\n```perl6\nfor $pg.cursor('select * from foo where x = $1', 27) -\u003e @row\n{\n    say @row;\n}\n```\n\nThe `cursor` method will fetch *N* rows at a time from the server (can\nbe controlled with the `:fetch` parameter, defaults to 1,000).  The\n`:hash` parameter can be used to retrieve hashes for the rows instead\nof arrays.\n\n```perl6\nfor $pg.cursor('select * from foo', fetch =\u003e 500, :hash) -\u003e %r\n{\n    say %r;\n}\n```\n\nBulk Copy In\n------------\n\nPostgreSQL has a\n[COPY](https://www.postgresql.org/docs/10/static/sql-copy.html)\nfacility for bulk copy in and out of the database.\n\nThis is accessed with the `DB::Pg::Database` methods `.copy-data` and\n`.copy-end`.  Pass blocks of data in with `.copy-data`, and call\n`.copy-end` when complete.\n\n```perl6\nmy $db = $pg.db;\n$db.query('copy foo from stdin (format csv)'); # Any valid COPY command\n$db.copy-data(\"1,2\\n4,5\\n6,12\\n\")\n$db.copy-end;\n$db.finish;\n```\n\nAs a convenience, these methods return the database object, so they\ncan easily be chained (though you will probably loop the `copy-data`\ncall.)\n\n```perl6\n$pg.db.execute('copy foo from stdin').copy-data(\"1 2\\n12 34234\\n\").copy-end.finish;\n```\n\nBulk Copy Out\n-------------\n\nBulk copy out can performed too, a COPY command will return a sequence\nfrom an iterator which will return each line:\n\n```perl6\nfor $pg.query('copy foo to stdout (format csv)') -\u003e $line\n{\n    print $line;\n}\n```\n\nListen/Notify\n-------------\n\nPostgreSQL also supports an asynchronous\n[LISTEN](https://www.postgresql.org/docs/10/static/sql-listen.html)\ncommand that you can use to receive notifications from the database.\nThe `.listen()` method returns a supply that can be used within a\n`react` block.  You can listen to multiple channels, and all listens\nwill share the same database connection.\n\n```perl6\nreact {\n    whenever $pg.listen('foo') -\u003e $msg\n    {\n        say $msg;\n    }\n    whenever $pg.listen('bar') -\u003e $msg\n    {\n        say $msg;\n    }\n}\n```\n\nUse `.unlisten` to stop listening to a specific channel.  When the\nlast listened supply is unlistened, the react block will exit.\n\n```perl6\n$pg.unlisten('foo')\n```\n\nPostgreSQL notifications can be sent with the `.notify` method:\n\n```perl6\n$pg.notify('foo', 'a message');\n```\n\nFor now, `listen()` requires the `epoll` module to be installed, and\nwill `die` if it isn't installed.\n\nType Conversions\n----------------\n\nThe `DB::Pg::Converter` object is used to convert between\nPostgreSQL types and Perl types.  It has two maps, one from\n[oid](https://www.postgresql.org/docs/current/static/datatype-oid.html)\ntypes to PostgreSQL type names, and one from the type names to Perl\ntypes.\n\nFor example, the oid `23` maps to the PostgreSQL type `int` which maps\nto the Perl type `Int`.\n\n`DB::Pg::Converter` has a multiple dispatch method `convert()`\nthat is used to convert types.\n\nExtra roles can be mixed in to the default converter to enable it to\nconvert to and from other types.\n\nThe `converter()` method on the main `DB::Pg` object will return the\ncurrent converter, then `does` can be used to add a role with extra\nconversion methods.\n\nHere is a short example that causes the PostgreSQL 'json' and 'jsonb'\ntypes to be converted automatically.\n\n```perl6\nuse DB::Pg;\nuse JSON::Fast;\n\nmy $pg = DB::Pg.new;\n\nmy class JSON {}  # Just a fake type, since JSON uses native Perl arrays/hashes\n\n$pg.converter does role JSONConverter\n{\n    submethod BUILD { self.add-type(json =\u003e JSON, jsonb =\u003e JSON) }\n    multi method convert(JSON:U, Str:D $value) { from-json($value) }\n    multi method convert(Mu:D $value, JSON:U) { to-json($value) }\n}\n```\n\nThere are three parts to this conversion.  First the `BUILD` adds the\ntype mappings, then there are two methods, the first converts from a\nstring (`Str:D`) to a `JSON:U` type.  The second will be used when a\nparameter requires a JSON object.  If the object already has a `Str`\nmethod that results in a suitable string for PostgreSQL (often the\ncase), the second method can be omitted.  (Or if you are only reading\na type from the database, and never passing it to the server.)\n\nSeveral Converters are bundled with this module, and by default\nthey are added to the Converter automatically:\n\n* DateTime (date, timestamp, timestamptz -\u003e Date, DateTime)\n* JSON (json, jsonb)\n* UUID (uuid -\u003e UUID via LibUUID)\n* Geometric (point, line, lseg, box, path, polygon, circle)\n\nThe Geometric types are available in `DB::Pg::GeometricTypes`.\n\nThe `DateTime` converter relies on the PostgreSQL `datestyle`\nconfiguration.  It must be set to a style compatible with Raku to be\nable to convert.  In particular, some legacy styles still use 2 digit\nyear representations that are impossible to unambiguosly map to a\nspecific date.  It is recommended that you set `datestyle` to 'iso'.\nThis can either be done for the whole server (in `postgresql.conf`, or\n'alter database \u003csome database\u003e set datestyle iso'), or per client\n(with 'set datestyle to iso' or by setting environment variable\n`PGDATESTYLE` to `iso`).\n\nIf you *don't* want any of those converters, just pass in an empty\n`converters` array, or with just the ones you want:\n\n```perl6\nmy $pg = DB::Pg.new(converters =\u003e \u003cDateTime JSON\u003e)\n```\n\nIf you want a different type of conversion than those canned types,\njust exclude the default one and install your own as above.\n\nNote: I'm looking for better ways to arrange this -- comments (file an\nissue) welcome!\n\nArrays\n------\n\nMost types of arrays are handled by default.  When selecting, they\nwill be converted to Perl Array objects.  Likewise, to pass arrays to\nthe server, just pass a Perl Array object.\n\nExceptions\n----------\n\nAll database errors, including broken SQL queries, are thrown as exceptions.\n\nExceptions for a query result may have additional fields as reported by PostgreSQL with the mapping below (perl exception field ➡ PostgreSQL field name):\n\n* message ➡ PG_DIAG_MESSAGE_PRIMARY\n* message-detail ➡ PG_DIAG_MESSAGE_DETAIL\n* message-hint ➡ PG_DIAG_MESSAGE_HINT\n* context ➡ PG_DIAG_CONTEXT\n* type ➡ PG_DIAG_SEVERITY_NONLOCALIZED\n* type-localized ➡ PG_DIAG_SEVERITY\n* state ➡ PG_DIAG_SQLSTATE\n* statement-position ➡ PG_DIAG_STATEMENT_POSITION\n* internal-position ➡ PG_DIAG_INTERNAL_POSITION\n* internal-query ➡ PG_DIAG_INTERNAL_QUERY\n* schema ➡ PG_DIAG_SCHEMA_NAME\n* table ➡ PG_DIAG_TABLE_NAME\n* column ➡ PG_DIAG_COLUMN_NAME\n* datatype ➡ PG_DIAG_DATATYPE_NAME\n* constraint ➡ PG_DIAG_CONSTRAINT_NAME\n* source-file ➡ PG_DIAG_SOURCE_FILE\n* source-line ➡ PG_DIAG_SOURCE_LINE\n* source-function ➡ PG_DIAG_SOURCE_FUNCTION\n\nPlease see the [PostgreSQL documentation](https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQRESULTERRORFIELD)\nfor a detailed description of what each field contains.\n\nNOTE\n----\n\nFor now, I've got the async pub/stuff using epoll, which is Linux\nspecific, so this is tied to Linux.  Patches welcome!\n\nAcknowledgements\n----------------\n\nInspiration taken from the existing Perl6\n[DBIish](https://github.com/perl6/DBIish) module as well as the Perl 5\n[Mojo::Pg](http://mojolicious.org/perldoc/Mojo/Pg) from the\nMojolicious project.\n\nLicense\n-------\n\nSee [NASA Open Source Agreement](../master/NASA_Open_Source_Agreement_1.3%20GSC-18031.pdf) for more details.\n\nCopyright\n---------\n\nCopyright © 2017 United States Government as represented by the\nAdministrator of the National Aeronautics and Space Administration.\nNo copyright is claimed in the United States under Title 17,\nU.S.Code. All Other Rights Reserved.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurttilmes%2Fraku-dbpg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcurttilmes%2Fraku-dbpg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurttilmes%2Fraku-dbpg/lists"}