NAME
DBIO::GraphQL - Auto-generate a GraphQL schema from a DBIO schema
VERSION
version 0.900000
SYNOPSIS
use DBIO::GraphQL;
use GraphQL::Execution qw(execute);
my $db = My::Schema->connect(...);
my $result = DBIO::GraphQL->to_graphql($db);
# Simple plural query - always include at least one scalar field
# alongside nodes (e.g. total) - see KNOWN BEHAVIOUR below.
execute($result->{schema},
'{ allBooks { total nodes { title } } }',
undef, $result->{context});
# Filtered + paginated + ordered (nested-DBIO-style filter)
execute($result->{schema}, '
query {
allBooks(
filter: { title: { like: "%Perl%" } }
orderBy: { field: "title", direction: ASC }
page: { skip: 0, take: 5 }
) {
total
hasNextPage
nodes { id title }
}
}', undef, $result->{context});
# Cursor pagination
execute($result->{schema}, '
query($after: String) {
allBooks(cursor: { after: $after, first: 5 }) {
total
nextCursor
hasNextPage
nodes { id title }
}
}', undef, $result->{context}, { after => $cursor });
# Mutation
execute($result->{schema},
'mutation { createBook(title: "Dune", author_id: 4) { id title } }',
undef, $result->{context});
DESCRIPTION
Introspects every source registered with the supplied DBIO::Schema and builds a complete, executable GraphQL::Schema with:
One scalar field per column, typed as
Int,Float,Boolean, orStringbased on the column's declareddata_type.One relationship field per DBIO relationship (
has_manyresolves to a List type;belongs_to/might_haveresolve to a single object type). Build-time errors are emitted when the DBIO relationship contract is incomplete (see DBIO::GraphQL::Relationship).A root
Querytype with singular lookup and pluralall<Source>squeries supporting filtering, ordering, and both offset and cursor pagination. Filter arguments use a nested per-column shape that mirrors the DBIO search-condition format (see DBIO::GraphQL::Filter).A root
Mutationtype withcreateX,updateX, anddeleteXentry points for every source (see DBIO::GraphQL::Mutation).
Composite primary keys are fully supported throughout.
METHODS
to_graphql
my $result = DBIO::GraphQL->to_graphql($db);
Class method. Accepts a connected DBIO::Schema instance. Returns a hashref:
{
schema => $graphql_schema, # GraphQL::Schema, pass to execute()
context => $db, # the original schema, for convenience
}
SCALAR TYPE MAPPING
SQL column types are mapped to GraphQL scalars in the following priority order:
Boolean: bool, boolean, tinyint(1)
Float : float, double, double precision, real, money, decimal, numeric
Int : int, integer, bigint, smallint, tinyint, mediumint, serial
String : everything else (safe fallback)
The mapping is centralised in DBIO::GraphQL::ScalarMap and reused by the filter, mutation, and relationship modules.
PLURAL QUERIES
Every source X gets an allXs query returning an XConnection:
type XConnection {
nodes: [X]
total: Int # total rows matching filter, before pagination
nextCursor: String # opaque cursor; set only during cursor pagination
hasNextPage: Boolean # true when more pages follow
}
Always request total or another scalar field alongside nodes in your selection set - see "KNOWN BEHAVIOUR".
Filtering
Filter arguments use a nested per-column shape that mirrors the DBIO search-condition format. Each column accepts a typed *Filter input with operators that depend on the column's scalar type (IntFilter, FloatFilter, StringFilter, BoolFilter).
allBooks(filter: {
title: { like: "%Perl%" }
author_id: { gt: 3 }
active: { eq: true }
AND: [
{ title: { contains: "Hobbit" } }
{ OR: [ { title: { contains: "Ring" } }, { author_id: { gt: 5 } } ] }
]
}) { total nodes { title } }
Per-scalar operators:
IntFilter / FloatFilter
eq, not, gt, gte, lt, lte, in, isNull
StringFilter
eq, not, like, contains, startsWith, endsWith, in, isNull
BoolFilter
eq, not, isNull
Logical combinators (available on every per-source filter):
AND: [ Filter, Filter, ... ]
OR: [ Filter, Filter, ... ]
Ordering
allBooks(orderBy: { field: "title", direction: ASC }) {
total nodes { title }
}
direction is the OrderDirection enum: ASC or DESC. When omitted, results are ordered by primary key ascending.
Offset pagination
allBooks(page: { skip: 10, take: 5 }) { total nodes { title } }
skip defaults to 0, take defaults to 10.
Cursor pagination
# First page
allBooks(cursor: { first: 5 }) {
total nextCursor hasNextPage nodes { title }
}
# Subsequent pages - pass nextCursor from the previous response
allBooks(cursor: { after: "...", first: 5 }) {
total nextCursor hasNextPage nodes { title }
}
first defaults to 10. Cursor pagination takes precedence over offset pagination if both are supplied in the same query. Cursors are opaque base64-encoded strings derived from the row's primary key and should be treated as implementation details subject to change.
MUTATIONS
For every source X, three mutations are generated by DBIO::GraphQL::Mutation:
createX
mutation { createBook(title: "Dune", author_id: 4) { id title } }
Accepts all column values as arguments. Columns that are non-nullable, have no declared default, and are not auto-increment are wrapped in NonNull and must be supplied. On failure, dies - the error appears in the top-level errors array of the response.
updateX
mutation { updateBook(id: 1, title: "Dune Messiah") { id title } }
Identifies the target row by its primary key or by a complete set of columns from any unique constraint declared on the source. All non-key columns must be supplied (full update). dies if the row cannot be found or the update fails.
deleteX
mutation { deleteBook(id: 1) }
Identifies the target row by primary key or unique constraint. Returns true (Boolean) on success, false if the row is not found.
ERROR HANDLING
createXandupdateXresolver failuresdie. GraphQL catches the exception and surfaces it in the top-levelerrorsarray; thedatafield for the failed mutation will benull.deleteXreturnsfalserather than dying when a row is not found.Relationship resolution
dies at build time when the DBIO relationship contract is incomplete (see DBIO::GraphQL::Relationship). Seton_error => 'warn'on the resolver to downgrade to a warning + silent skip.
LIMITATIONS
Relationship fields are unfiltered. Relationship fields within a query (e.g.
author { books { title } }) return all related rows. They do not acceptfilter,orderBy, or pagination arguments.No nested input for mutations.
createXandupdateXaccept only scalar column values. Related rows must be created or linked separately using their own mutations and raw foreign-key values.updateX is a full update. All non-primary-key columns must be supplied; partial (sparse) updates are not supported.
Cursor pagination assumes primary-key order. The
aftercursor encodes the primary key of the last returned row and applies apk > valuecondition. If you supply a customorderByusing a non-primary-key column, the cursor will not advance correctly. Use offset pagination (page) when ordering by non-PK columns.No custom scalars. Column types such as
date,datetime,json, anduuidall map toString. Custom GraphQL scalars are not generated.No subscriptions. Only
QueryandMutationoperation types are generated.col_like case sensitivity is database-dependent. SQLite
LIKEis case-insensitive for ASCII characters but case-sensitive for Unicode. PostgreSQLLIKEis always case-sensitive.
KNOWN BEHAVIOUR
When querying a plural allXs field, always include at least one scalar field (total, hasNextPage, or nextCursor) alongside nodes in your selection set:
# Correct
{ allBooks { total nodes { title } } }
# May silently return empty nodes in some GraphQL executor versions
{ allBooks { nodes { title } } }
This is a quirk of how GraphQL::Execution resolves connection object types when the selection set contains only a list field.
ARCHITECTURE
DBIO::GraphQL is a thin orchestrator over four focused modules:
DBIO::GraphQL::ScalarMap - column data_type → GraphQL scalar
DBIO::GraphQL::Filter (and DBIO::GraphQL::Filter::Search, DBIO::GraphQL::Filter::Null) - per-source GraphQL InputObject translating nested filter args into DBIO search conditions
DBIO::GraphQL::Relationship - relationship field resolution with strict contract validation (closes KARR #1)
DBIO::GraphQL::Mutation - createX / updateX / deleteX per source
ACKNOWLEDGEMENTS
DBIO port of DBIx::Class::Schema::GraphQL by Mohammad Sajid Anwar (MANWAR). The original DBIx::Class implementation, design, and documentation are his work; this distribution adapts them to the DBIO schema introspection API and re-architects the filter surface into a nested per-column shape that mirrors DBIO's native search-condition format.
SEE ALSO
DBIx::Class::Schema::GraphQL, GraphQL::Plugin::Convert::DBIC, DBIO::Schema, GraphQL::Schema
AUTHOR
DBIO Authors
COPYRIGHT AND LICENSE
Copyright (C) 2026 DBIO Authors
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.