| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package GraphQL::Plugin::Convert::DBIC; | 
| 2 | 5 |  |  | 5 |  | 5089 | use strict; | 
|  | 5 |  |  |  |  | 11 |  | 
|  | 5 |  |  |  |  | 180 |  | 
| 3 | 5 |  |  | 5 |  | 30 | use warnings; | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 133 |  | 
| 4 | 5 |  |  | 5 |  | 2656 | use GraphQL::Schema; | 
|  | 5 |  |  |  |  | 8574017 |  | 
|  | 5 |  |  |  |  | 316 |  | 
| 5 | 5 |  |  | 5 |  | 46 | use GraphQL::Debug qw(_debug); | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 261 |  | 
| 6 | 5 |  |  | 5 |  | 3453 | use Lingua::EN::Inflect::Number qw(to_S to_PL); | 
|  | 5 |  |  |  |  | 157213 |  | 
|  | 5 |  |  |  |  | 46 |  | 
| 7 | 5 |  |  | 5 |  | 1357 | use Carp qw(confess); | 
|  | 5 |  |  |  |  | 14 |  | 
|  | 5 |  |  |  |  | 382 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | our $VERSION = "0.16"; | 
| 10 | 5 |  |  | 5 |  | 40 | use constant DEBUG => $ENV{GRAPHQL_DEBUG}; | 
|  | 5 |  |  |  |  | 13 |  | 
|  | 5 |  |  |  |  | 9515 |  | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | my %GRAPHQL_TYPE2SQLS = ( | 
| 13 |  |  |  |  |  |  | String => [ | 
| 14 |  |  |  |  |  |  | 'wlongvarchar', | 
| 15 |  |  |  |  |  |  | 'guid', | 
| 16 |  |  |  |  |  |  | 'uuid', | 
| 17 |  |  |  |  |  |  | 'wvarchar', | 
| 18 |  |  |  |  |  |  | 'wchar', | 
| 19 |  |  |  |  |  |  | 'longvarbinary', | 
| 20 |  |  |  |  |  |  | 'varbinary', | 
| 21 |  |  |  |  |  |  | 'binary', | 
| 22 |  |  |  |  |  |  | 'longvarchar', | 
| 23 |  |  |  |  |  |  | 'unknown_type', | 
| 24 |  |  |  |  |  |  | 'all_types', | 
| 25 |  |  |  |  |  |  | 'char', | 
| 26 |  |  |  |  |  |  | 'varchar', | 
| 27 |  |  |  |  |  |  | 'udt', | 
| 28 |  |  |  |  |  |  | 'udt_locator', | 
| 29 |  |  |  |  |  |  | 'row', | 
| 30 |  |  |  |  |  |  | 'ref', | 
| 31 |  |  |  |  |  |  | 'blob', | 
| 32 |  |  |  |  |  |  | 'blob_locator', | 
| 33 |  |  |  |  |  |  | 'clob', | 
| 34 |  |  |  |  |  |  | 'clob_locator', | 
| 35 |  |  |  |  |  |  | 'array', | 
| 36 |  |  |  |  |  |  | 'array_locator', | 
| 37 |  |  |  |  |  |  | 'multiset', | 
| 38 |  |  |  |  |  |  | 'multiset_locator', | 
| 39 |  |  |  |  |  |  | # mysql | 
| 40 |  |  |  |  |  |  | 'set', | 
| 41 |  |  |  |  |  |  | 'text', | 
| 42 |  |  |  |  |  |  | 'tinytext', | 
| 43 |  |  |  |  |  |  | 'mediumtext', | 
| 44 |  |  |  |  |  |  | 'longtext', | 
| 45 |  |  |  |  |  |  | # pgsql | 
| 46 |  |  |  |  |  |  | 'cidr', | 
| 47 |  |  |  |  |  |  | 'inet', | 
| 48 |  |  |  |  |  |  | ], | 
| 49 |  |  |  |  |  |  | Int => [ | 
| 50 |  |  |  |  |  |  | 'bigint', | 
| 51 |  |  |  |  |  |  | 'bit', | 
| 52 |  |  |  |  |  |  | 'tinyint', | 
| 53 |  |  |  |  |  |  | 'integer', | 
| 54 |  |  |  |  |  |  | 'smallint', | 
| 55 |  |  |  |  |  |  | 'mediumint', | 
| 56 |  |  |  |  |  |  | 'interval', | 
| 57 |  |  |  |  |  |  | 'interval_year', | 
| 58 |  |  |  |  |  |  | 'interval_month', | 
| 59 |  |  |  |  |  |  | 'interval_day', | 
| 60 |  |  |  |  |  |  | 'interval_hour', | 
| 61 |  |  |  |  |  |  | 'interval_minute', | 
| 62 |  |  |  |  |  |  | 'interval_second', | 
| 63 |  |  |  |  |  |  | 'interval_year_to_month', | 
| 64 |  |  |  |  |  |  | 'interval_day_to_hour', | 
| 65 |  |  |  |  |  |  | 'interval_day_to_minute', | 
| 66 |  |  |  |  |  |  | 'interval_day_to_second', | 
| 67 |  |  |  |  |  |  | 'interval_hour_to_minute', | 
| 68 |  |  |  |  |  |  | 'interval_hour_to_second', | 
| 69 |  |  |  |  |  |  | 'interval_minute_to_second', | 
| 70 |  |  |  |  |  |  | # not DBI SQL_* types | 
| 71 |  |  |  |  |  |  | 'int', | 
| 72 |  |  |  |  |  |  | ], | 
| 73 |  |  |  |  |  |  | Float => [ | 
| 74 |  |  |  |  |  |  | 'numeric', | 
| 75 |  |  |  |  |  |  | 'decimal', | 
| 76 |  |  |  |  |  |  | 'float', | 
| 77 |  |  |  |  |  |  | 'real', | 
| 78 |  |  |  |  |  |  | 'double', | 
| 79 |  |  |  |  |  |  | ], | 
| 80 |  |  |  |  |  |  | DateTime => [ | 
| 81 |  |  |  |  |  |  | 'datetime', | 
| 82 |  |  |  |  |  |  | 'date', | 
| 83 |  |  |  |  |  |  | 'time', | 
| 84 |  |  |  |  |  |  | 'timestamp', | 
| 85 |  |  |  |  |  |  | 'type_date', | 
| 86 |  |  |  |  |  |  | 'type_time', | 
| 87 |  |  |  |  |  |  | 'type_timestamp', | 
| 88 |  |  |  |  |  |  | 'type_time_with_timezone', | 
| 89 |  |  |  |  |  |  | 'type_timestamp_with_timezone', | 
| 90 |  |  |  |  |  |  | # pgsql | 
| 91 |  |  |  |  |  |  | 'timestamp with time zone', | 
| 92 |  |  |  |  |  |  | 'timestamp without time zone', | 
| 93 |  |  |  |  |  |  | ], | 
| 94 |  |  |  |  |  |  | Boolean => [ | 
| 95 |  |  |  |  |  |  | 'boolean', | 
| 96 |  |  |  |  |  |  | ], | 
| 97 |  |  |  |  |  |  | ID => [ | 
| 98 |  |  |  |  |  |  | 'wvarchar', | 
| 99 |  |  |  |  |  |  | ], | 
| 100 |  |  |  |  |  |  | ); | 
| 101 |  |  |  |  |  |  | my %TYPEMAP = ( | 
| 102 |  |  |  |  |  |  | (map { | 
| 103 |  |  |  |  |  |  | my $gql_type = $_; | 
| 104 |  |  |  |  |  |  | map { | 
| 105 |  |  |  |  |  |  | ($_ => $gql_type) | 
| 106 |  |  |  |  |  |  | } @{ $GRAPHQL_TYPE2SQLS{$gql_type} } | 
| 107 |  |  |  |  |  |  | } keys %GRAPHQL_TYPE2SQLS), | 
| 108 |  |  |  |  |  |  | enum => sub { | 
| 109 |  |  |  |  |  |  | my ($source, $column, $info) = @_; | 
| 110 |  |  |  |  |  |  | my $extra = $info->{extra}; | 
| 111 |  |  |  |  |  |  | return { | 
| 112 |  |  |  |  |  |  | kind => 'enum', | 
| 113 |  |  |  |  |  |  | name => _dbicsource2pretty( | 
| 114 |  |  |  |  |  |  | $extra->{custom_type_name} || "${source}_$column" | 
| 115 |  |  |  |  |  |  | ), | 
| 116 |  |  |  |  |  |  | values => { map { _trim_name($_) => { value => $_ } } @{ $extra->{list} } }, | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  | }, | 
| 119 |  |  |  |  |  |  | ); | 
| 120 |  |  |  |  |  |  | my %TYPE2SCALAR = map { ($_ => 1) } qw(ID String Int Float Boolean); | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | sub _dbicsource2pretty { | 
| 123 | 83 |  |  | 83 |  | 183 | my ($source) = @_; | 
| 124 | 83 | 50 |  |  |  | 189 | confess "_dbicsource2pretty given undef" if !defined $source; | 
| 125 | 83 |  | 66 |  |  | 143 | $source = eval { $source->source_name } || $source; | 
| 126 | 83 |  |  |  |  | 6399 | $source =~ s#.*::##; | 
| 127 | 83 |  |  |  |  | 303 | $source = to_S $source; | 
| 128 | 83 |  |  |  |  | 77357 | join '', map ucfirst, split /_+/, $source; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | sub _trim_name { | 
| 132 | 38 |  |  | 38 |  | 102 | my ($name) = @_; | 
| 133 | 38 | 50 |  |  |  | 83 | return if !defined $name; | 
| 134 | 38 |  |  |  |  | 112 | $name =~ s#[^a-zA-Z0-9_]+#_#g; | 
| 135 | 38 |  |  |  |  | 176 | $name; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | sub _apply_modifier { | 
| 139 | 854 |  |  | 854 |  | 1530 | my ($modifier, $typespec) = @_; | 
| 140 | 854 | 100 |  |  |  | 1566 | return $typespec if !$modifier; | 
| 141 | 741 | 100 | 100 |  |  | 2328 | return $typespec if $modifier eq 'non_null' | 
|  |  |  | 100 |  |  |  |  | 
| 142 |  |  |  |  |  |  | and ref $typespec eq 'ARRAY' | 
| 143 |  |  |  |  |  |  | and $typespec->[0] eq 'non_null'; # no double-non_null | 
| 144 | 678 |  |  |  |  | 2237 | [ $modifier, { type => $typespec } ]; | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | sub _remove_modifiers { | 
| 148 | 68 |  |  | 68 |  | 124 | my ($typespec) = @_; | 
| 149 | 68 | 100 |  |  |  | 197 | return _remove_modifiers($typespec->{type}) if ref $typespec eq 'HASH'; | 
| 150 | 50 | 100 |  |  |  | 162 | return $typespec if ref $typespec ne 'ARRAY'; | 
| 151 | 18 |  |  |  |  | 44 | _remove_modifiers($typespec->[1]); | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | sub _type2createinput { | 
| 155 | 28 |  |  | 28 |  | 93 | my ($name, $fields, $pk21, $fk21, $column21, $name2type) = @_; | 
| 156 |  |  |  |  |  |  | +{ | 
| 157 |  |  |  |  |  |  | kind => 'input', | 
| 158 |  |  |  |  |  |  | name => "${name}CreateInput", | 
| 159 |  |  |  |  |  |  | fields => { | 
| 160 | 162 |  |  |  |  | 309 | (map { ($_ => $fields->{$_}) } | 
| 161 | 28 |  | 100 |  |  | 343 | grep !$pk21->{$_} && !$fk21->{$_}, keys %$column21), | 
| 162 |  |  |  |  |  |  | _make_fk_fields($name, $fk21, $name2type), | 
| 163 |  |  |  |  |  |  | }, | 
| 164 |  |  |  |  |  |  | }; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | sub _type2idinput { | 
| 168 | 28 |  |  | 28 |  | 67 | my ($name, $fields, $pk21) = @_; | 
| 169 |  |  |  |  |  |  | +{ | 
| 170 |  |  |  |  |  |  | kind => 'input', | 
| 171 |  |  |  |  |  |  | name => "${name}IDInput", | 
| 172 |  |  |  |  |  |  | fields => { | 
| 173 | 28 |  |  |  |  | 98 | (map { ($_ => $fields->{$_}) } | 
|  | 27 |  |  |  |  | 133 |  | 
| 174 |  |  |  |  |  |  | keys %$pk21), | 
| 175 |  |  |  |  |  |  | }, | 
| 176 |  |  |  |  |  |  | }; | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | sub _type2searchinput { | 
| 180 | 30 |  |  | 30 |  | 71 | my ($name, $column2rawtype, $pk21, $column21) = @_; | 
| 181 |  |  |  |  |  |  | +{ | 
| 182 |  |  |  |  |  |  | kind => 'input', | 
| 183 |  |  |  |  |  |  | name => "${name}SearchInput", | 
| 184 |  |  |  |  |  |  | fields => { | 
| 185 | 171 |  |  |  |  | 536 | (map { ($_ => { type => $column2rawtype->{$_} }) } | 
| 186 | 30 |  |  |  |  | 149 | grep !$pk21->{$_}, keys %$column21), | 
| 187 |  |  |  |  |  |  | }, | 
| 188 |  |  |  |  |  |  | }; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | sub _type2updateinput { | 
| 192 | 28 |  |  | 28 |  | 62 | my ($name) = @_; | 
| 193 |  |  |  |  |  |  | +{ | 
| 194 | 28 |  |  |  |  | 89 | kind => 'input', | 
| 195 |  |  |  |  |  |  | name => "${name}UpdateInput", | 
| 196 |  |  |  |  |  |  | fields => { | 
| 197 |  |  |  |  |  |  | id => { type => _apply_modifier('non_null', "${name}IDInput") }, | 
| 198 |  |  |  |  |  |  | payload => { type => _apply_modifier('non_null', "${name}SearchInput") }, | 
| 199 |  |  |  |  |  |  | }, | 
| 200 |  |  |  |  |  |  | }; | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | sub _make_fk_fields { | 
| 204 | 28 |  |  | 28 |  | 62 | my ($name, $fk21, $name2type) = @_; | 
| 205 | 28 |  |  |  |  | 56 | my $type = $name2type->{$name}; | 
| 206 |  |  |  |  |  |  | (map { | 
| 207 | 28 |  |  |  |  | 138 | my $field_type = $type->{fields}{$_}{type}; | 
|  | 17 |  |  |  |  | 45 |  | 
| 208 | 17 | 100 |  |  |  | 40 | if (!$TYPE2SCALAR{_remove_modifiers($field_type)}) { | 
| 209 | 15 |  | 66 |  |  | 53 | my $non_null = | 
| 210 |  |  |  |  |  |  | ref($field_type) eq 'ARRAY' && $field_type->[0] eq 'non_null'; | 
| 211 | 15 |  | 100 |  |  | 58 | $field_type = _apply_modifier( | 
| 212 |  |  |  |  |  |  | $non_null && 'non_null', _remove_modifiers($field_type)."IDInput" | 
| 213 |  |  |  |  |  |  | ); | 
| 214 |  |  |  |  |  |  | } | 
| 215 | 17 |  |  |  |  | 196 | ($_ => { type => $field_type }) | 
| 216 |  |  |  |  |  |  | } keys %$fk21); | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | sub field_resolver { | 
| 220 | 64 |  |  | 64 | 1 | 1186546 | my ($root_value, $args, $context, $info) = @_; | 
| 221 | 64 |  |  |  |  | 164 | my $field_name = $info->{field_name}; | 
| 222 | 64 |  |  |  |  | 110 | DEBUG and _debug('DBIC.resolver', $field_name, $args, $info); | 
| 223 | 64 |  |  |  |  | 192 | my $parent_name = $info->{parent_type}->name; | 
| 224 | 64 | 100 |  |  |  | 254 | if ($parent_name eq 'Mutation') { | 
|  |  | 100 |  |  |  |  |  | 
| 225 | 3 |  |  |  |  | 18 | goto &_mutation_resolver; | 
| 226 |  |  |  |  |  |  | } elsif ($parent_name eq 'Query') { | 
| 227 | 8 |  |  |  |  | 48 | goto &_query_resolver; | 
| 228 |  |  |  |  |  |  | } | 
| 229 |  |  |  |  |  |  | my $property = ref($root_value) eq 'HASH' | 
| 230 | 53 | 100 |  |  |  | 173 | ? $root_value->{$field_name} | 
| 231 |  |  |  |  |  |  | : $root_value; | 
| 232 | 53 | 50 |  |  |  | 160 | return $property->($args, $context, $info) if ref $property eq 'CODE'; | 
| 233 | 53 | 100 | 50 |  |  | 292 | return $property // die "DBIC.resolver could not resolve '$field_name'\n" | 
|  |  |  | 66 |  |  |  |  | 
| 234 |  |  |  |  |  |  | if ref $root_value eq 'HASH' or !$root_value->can($field_name); | 
| 235 | 8 | 50 |  |  |  | 35 | return $root_value->$field_name($args, $context, $info) | 
| 236 |  |  |  |  |  |  | if !UNIVERSAL::isa($root_value, 'DBIx::Class::Core'); | 
| 237 |  |  |  |  |  |  | # dbic search | 
| 238 | 8 |  |  |  |  | 221 | my $rs = $root_value->$field_name; | 
| 239 | 8 | 50 |  |  |  | 5847 | $rs = [ $rs->all ] if $info->{return_type}->isa('GraphQL::Type::List'); | 
| 240 | 8 |  |  |  |  | 33 | return $rs; | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | sub _subfieldrels { | 
| 244 | 13 |  |  | 13 |  | 32 | my ($field_node) = @_; | 
| 245 | 13 | 50 |  |  |  | 49 | die "_subfieldrels called on non-field" if $field_node->{kind} ne 'field'; | 
| 246 | 13 | 50 |  |  |  | 26 | return {} unless my @sels = @{ $field_node->{selections} || [] }; | 
|  | 13 | 50 |  |  |  | 67 |  | 
| 247 | 13 | 100 |  |  |  | 32 | return {} unless my @withsels = grep @{ $_->{selections} || [] }, @sels; | 
|  | 27 | 100 |  |  |  | 163 |  | 
| 248 | 4 |  |  |  |  | 9 | +{ map { $_->{name} => _subfieldrels($_) } @withsels }; | 
|  | 5 |  |  |  |  | 21 |  | 
| 249 |  |  |  |  |  |  | } | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | sub _query_resolver { | 
| 252 | 8 |  |  | 8 |  | 30 | my ($dbic_schema, $args, $context, $info) = @_; | 
| 253 | 8 |  |  |  |  | 189 | my $name = $info->{return_type}->name; | 
| 254 | 8 | 100 |  |  |  | 279 | my $method = $info->{return_type}->isa('GraphQL::Type::List') | 
| 255 |  |  |  |  |  |  | ? 'search' : 'find'; | 
| 256 | 8 |  |  |  |  | 19 | my @subfieldrels = map _subfieldrels($_), @{$info->{field_nodes}}; | 
|  | 8 |  |  |  |  | 42 |  | 
| 257 | 8 | 100 |  |  |  | 38 | $args = $args->{input} if ref $args->{input} eq 'HASH'; | 
| 258 | 8 |  |  |  |  | 33 | $args = +{ map { ("me.$_" => $args->{$_}) } keys %$args }; | 
|  | 8 |  |  |  |  | 44 |  | 
| 259 | 8 |  |  |  |  | 20 | DEBUG and _debug('DBIC.root_value', $name, $method, $args, \@subfieldrels, $info); | 
| 260 | 8 |  |  |  |  | 84 | my $rs = $dbic_schema->resultset($name); | 
| 261 | 8 |  |  |  |  | 5182 | my $result = $rs->$method( | 
| 262 |  |  |  |  |  |  | $args, | 
| 263 |  |  |  |  |  |  | { | 
| 264 |  |  |  |  |  |  | prefetch => { map %$_, @subfieldrels }, | 
| 265 |  |  |  |  |  |  | result_class => 'DBIx::Class::ResultClass::HashRefInflator' | 
| 266 |  |  |  |  |  |  | }, | 
| 267 |  |  |  |  |  |  | ); | 
| 268 | 8 | 100 |  |  |  | 8059 | $result = [ $result->all ] if $method eq 'search'; | 
| 269 | 8 |  |  |  |  | 133600 | $result; | 
| 270 |  |  |  |  |  |  | } | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | sub _make_query_pk_field { | 
| 273 | 52 |  |  | 52 |  | 118 | my ($typename, $type, $name2pk21, $is_list) = @_; | 
| 274 | 52 |  |  |  |  | 90 | my $return_type = $typename; | 
| 275 | 52 | 100 |  |  |  | 154 | $return_type = _apply_modifier('list', $return_type) if $is_list; | 
| 276 |  |  |  |  |  |  | +{ | 
| 277 |  |  |  |  |  |  | type => $return_type, | 
| 278 |  |  |  |  |  |  | args => { | 
| 279 |  |  |  |  |  |  | map { | 
| 280 | 54 |  |  |  |  | 126 | my $field_type = _apply_modifier('non_null', $type->{fields}{$_}{type}); | 
| 281 | 54 | 100 |  |  |  | 145 | $field_type = _apply_modifier('non_null', _apply_modifier('list', | 
| 282 |  |  |  |  |  |  | $field_type | 
| 283 |  |  |  |  |  |  | )) if $is_list; | 
| 284 | 54 |  |  |  |  | 326 | $_ => { type => $field_type } | 
| 285 | 52 |  |  |  |  | 82 | } keys %{ $name2pk21->{$typename} } | 
|  | 52 |  |  |  |  | 127 |  | 
| 286 |  |  |  |  |  |  | }, | 
| 287 |  |  |  |  |  |  | }; | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | sub _make_input_field { | 
| 291 | 114 |  |  | 114 |  | 283 | my ($typename, $return_type, $mutation_kind, $list_in, $list_out) = @_; | 
| 292 | 114 | 50 |  |  |  | 282 | $return_type = _apply_modifier('list', $return_type) if $list_out; | 
| 293 | 114 |  |  |  |  | 302 | my $input_type = $typename . ucfirst($mutation_kind) . 'Input'; | 
| 294 | 114 |  |  |  |  | 198 | $input_type = _apply_modifier('non_null', $input_type); | 
| 295 | 114 | 100 |  |  |  | 277 | $input_type = _apply_modifier('non_null', _apply_modifier('list', | 
| 296 |  |  |  |  |  |  | $input_type | 
| 297 |  |  |  |  |  |  | )) if $list_in; | 
| 298 |  |  |  |  |  |  | +{ | 
| 299 | 114 |  |  |  |  | 747 | type => $return_type, | 
| 300 |  |  |  |  |  |  | args => { input => { type => $input_type } }, | 
| 301 |  |  |  |  |  |  | }; | 
| 302 |  |  |  |  |  |  | } | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | use constant MUTATE_ARGSPROCESS => { | 
| 305 | 2 |  |  |  |  | 20 | update => sub { $_[0]->{payload} }, | 
| 306 |  |  |  |  |  |  | delete => sub { }, | 
| 307 | 5 |  |  | 5 |  | 43 | }; | 
|  | 5 |  |  |  |  | 35 |  | 
|  | 5 |  |  |  |  | 788 |  | 
| 308 |  |  |  |  |  |  | use constant MUTATE_POSTPROCESS => { | 
| 309 | 2 | 50 |  |  |  | 32 | update => sub { ref($_[0]) eq 'GraphQL::Error' ? $_[0] : $_[0]->discard_changes }, | 
| 310 | 1 | 50 | 50 |  |  | 13 | delete => sub { ref($_[0]) eq 'GraphQL::Error' ? $_[0] : $_[0] && 1 }, | 
| 311 | 5 |  |  | 5 |  | 38 | }; | 
|  | 5 |  |  |  |  | 14 |  | 
|  | 5 |  |  |  |  | 6919 |  | 
| 312 |  |  |  |  |  |  | sub _mutation_resolver { | 
| 313 | 3 |  |  | 3 |  | 14 | my ($dbic_schema, $args, $context, $info) = @_; | 
| 314 | 3 |  |  |  |  | 10 | my $name = $info->{field_name}; | 
| 315 | 3 | 50 |  |  |  | 27 | die "Couldn't understand field '$name'" | 
| 316 |  |  |  |  |  |  | unless $name =~ s/^(create|update|delete)//; | 
| 317 | 3 |  |  |  |  | 12 | my $method = $1; | 
| 318 | 3 |  |  |  |  | 7 | my $find_first = $method ne 'create'; | 
| 319 | 3 |  |  |  |  | 19 | my ($args_process, $result_process) = map $_->{$method}, | 
| 320 |  |  |  |  |  |  | MUTATE_ARGSPROCESS, MUTATE_POSTPROCESS; | 
| 321 | 3 | 50 |  |  |  | 16 | $args = $args->{input} if $args->{input}; | 
| 322 | 3 |  |  |  |  | 12 | my $is_list = ref $args eq 'ARRAY'; | 
| 323 | 3 | 50 |  |  |  | 14 | $args = [ $args ] if !$is_list; # so can just deal as list below | 
| 324 | 3 |  |  |  |  | 5 | DEBUG and _debug("DBIC.root_value", $args); | 
| 325 | 3 |  |  |  |  | 21 | my $rs = $dbic_schema->resultset($name); | 
| 326 |  |  |  |  |  |  | my $all_result = [ | 
| 327 |  |  |  |  |  |  | map { | 
| 328 | 3 |  |  |  |  | 1252 | my $operand = $rs; | 
|  | 5 |  |  |  |  | 14 |  | 
| 329 | 5 | 100 |  |  |  | 39 | $operand = $operand->find($_->{id}) if $find_first; | 
| 330 | 5 | 100 |  |  |  | 16348 | my $result = $operand | 
|  |  | 100 |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | ? $operand->$method($args_process ? $args_process->($_) : $_) | 
| 332 |  |  |  |  |  |  | : GraphQL::Error->coerce("$name not found"); | 
| 333 | 5 | 100 | 100 |  |  | 63985 | $result = $result_process->($result) | 
| 334 |  |  |  |  |  |  | if $result_process and ref($result) ne 'GraphQL::Error'; | 
| 335 | 5 |  |  |  |  | 10577 | $result; | 
| 336 |  |  |  |  |  |  | } @$args | 
| 337 |  |  |  |  |  |  | ]; | 
| 338 | 3 | 50 |  |  |  | 36 | $all_result = $all_result->[0] if !$is_list; | 
| 339 | 3 |  |  |  |  | 28 | $all_result | 
| 340 |  |  |  |  |  |  | } | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | sub to_graphql { | 
| 343 | 6 |  |  | 6 | 0 | 251620 | my ($class, $dbic_schema) = @_; | 
| 344 | 6 | 50 | 50 |  |  | 53 | $dbic_schema = $dbic_schema->() if ((ref($dbic_schema)||'') eq 'CODE'); | 
| 345 | 6 |  |  |  |  | 14 | my @ast; | 
| 346 |  |  |  |  |  |  | my ( | 
| 347 | 6 |  |  |  |  | 19 | %name2type, %name2column21, %name2pk21, %name2fk21, | 
| 348 |  |  |  |  |  |  | %name2column2rawtype, %seentype, %name2isview, | 
| 349 |  |  |  |  |  |  | ); | 
| 350 | 6 |  |  |  |  | 34 | for my $source (map $dbic_schema->source($_), $dbic_schema->sources) { | 
| 351 | 30 |  |  |  |  | 1327 | my $name = _dbicsource2pretty($source); | 
| 352 | 30 |  |  |  |  | 69 | DEBUG and _debug("schema_dbic2graphql($name)", $source); | 
| 353 | 30 | 100 |  |  |  | 247 | $name2isview{$name} = 1 if $source->can('view_definition'); | 
| 354 | 30 |  |  |  |  | 62 | my %fields; | 
| 355 | 30 |  |  |  |  | 131 | my $columns_info = $source->columns_info; | 
| 356 | 30 |  |  |  |  | 1192 | $name2pk21{$name} = +{ map { ($_ => 1) } $source->primary_columns }; | 
|  | 33 |  |  |  |  | 291 |  | 
| 357 |  |  |  |  |  |  | my %rel2info = map { | 
| 358 | 30 |  |  |  |  | 346 | ($_ => $source->relationship_info($_)) | 
|  | 40 |  |  |  |  | 272 |  | 
| 359 |  |  |  |  |  |  | } $source->relationships; | 
| 360 | 30 |  |  |  |  | 476 | for my $column (keys %$columns_info) { | 
| 361 | 216 |  |  |  |  | 342 | my $info = $columns_info->{$column}; | 
| 362 | 216 |  |  |  |  | 293 | DEBUG and _debug("schema_dbic2graphql($name.col)", $column, $info); | 
| 363 | 216 |  |  |  |  | 471 | my $rawtype = $TYPEMAP{ lc $info->{data_type} }; | 
| 364 | 216 | 100 |  |  |  | 426 | if ( 'CODE' eq ref $rawtype ) { | 
| 365 | 13 |  |  |  |  | 36 | my $col_spec = $rawtype->($name, $column, $info); | 
| 366 | 13 | 100 |  |  |  | 54 | push @ast, $col_spec unless $seentype{$col_spec->{name}}; | 
| 367 | 13 |  |  |  |  | 27 | $rawtype = $col_spec->{name}; | 
| 368 | 13 |  |  |  |  | 50 | $seentype{$col_spec->{name}} = 1; | 
| 369 |  |  |  |  |  |  | } | 
| 370 | 216 |  |  |  |  | 427 | $name2column2rawtype{$name}->{$column} = $rawtype; | 
| 371 |  |  |  |  |  |  | my $fulltype = _apply_modifier( | 
| 372 | 216 |  | 100 |  |  | 797 | !$info->{is_nullable} && 'non_null', | 
|  |  |  | 50 |  |  |  |  | 
| 373 |  |  |  |  |  |  | $rawtype | 
| 374 | 0 |  |  |  |  | 0 | // die "'$column' unknown data type: @{[lc $info->{data_type}]}\n", | 
| 375 |  |  |  |  |  |  | ); | 
| 376 | 216 |  |  |  |  | 529 | $fields{$column} = +{ type => $fulltype }; | 
| 377 | 216 | 100 |  |  |  | 476 | $name2fk21{$name}->{$column} = 1 if $info->{is_foreign_key}; | 
| 378 | 216 |  |  |  |  | 478 | $name2column21{$name}->{$column} = 1; | 
| 379 |  |  |  |  |  |  | } | 
| 380 | 30 |  |  |  |  | 100 | for my $rel (keys %rel2info) { | 
| 381 | 40 |  |  |  |  | 77 | my $info = $rel2info{$rel}; | 
| 382 | 40 |  |  |  |  | 59 | DEBUG and _debug("schema_dbic2graphql($name.rel)", $rel, $info); | 
| 383 | 40 |  |  |  |  | 110 | my $type = _dbicsource2pretty($info->{source}); | 
| 384 | 40 |  |  |  |  | 128 | $rel =~ s/_id$//; # dumb heuristic | 
| 385 | 40 |  |  |  |  | 97 | delete $name2column21{$name}->{$rel}; # so it's not a "column" now | 
| 386 | 40 |  |  |  |  | 81 | delete $name2pk21{$name}{$rel}; # it's not a PK either | 
| 387 |  |  |  |  |  |  | # if it WAS a column, capture its non-null-ness | 
| 388 | 40 |  | 100 |  |  | 194 | my $non_null = ref(($fields{$rel} || {})->{type}) eq 'ARRAY'; | 
| 389 | 40 | 100 |  |  |  | 113 | $type = _apply_modifier('non_null', $type) if $non_null; | 
| 390 | 40 | 100 |  |  |  | 174 | $type = _apply_modifier('list', $type) if $info->{attrs}{accessor} eq 'multi'; | 
| 391 | 40 | 100 |  |  |  | 98 | $type = _apply_modifier('non_null', $type) if $non_null; # in case list | 
| 392 | 40 |  |  |  |  | 160 | $fields{$rel} = +{ type => $type }; | 
| 393 |  |  |  |  |  |  | } | 
| 394 | 30 |  |  |  |  | 124 | my $spec = +{ | 
| 395 |  |  |  |  |  |  | kind => 'type', | 
| 396 |  |  |  |  |  |  | name => $name, | 
| 397 |  |  |  |  |  |  | fields => \%fields, | 
| 398 |  |  |  |  |  |  | }; | 
| 399 | 30 |  |  |  |  | 72 | $name2type{$name} = $spec; | 
| 400 | 30 |  |  |  |  | 136 | push @ast, $spec; | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  | push @ast, map _type2idinput( | 
| 403 |  |  |  |  |  |  | $_, $name2type{$_}->{fields}, $name2pk21{$_}, | 
| 404 |  |  |  |  |  |  | $name2column21{$_}, | 
| 405 | 6 |  | 66 |  |  | 85 | ), grep !$name2isview{$_} || keys %{ $name2pk21{$_} }, keys %name2type; | 
| 406 |  |  |  |  |  |  | push @ast, map _type2createinput( | 
| 407 |  |  |  |  |  |  | $_, $name2type{$_}->{fields}, $name2pk21{$_}, $name2fk21{$_}, | 
| 408 |  |  |  |  |  |  | $name2column21{$_}, \%name2type, | 
| 409 | 6 |  |  |  |  | 60 | ), grep !$name2isview{$_}, keys %name2type; | 
| 410 |  |  |  |  |  |  | push @ast, map _type2searchinput( | 
| 411 |  |  |  |  |  |  | $_, $name2column2rawtype{$_}, $name2pk21{$_}, | 
| 412 | 6 |  |  |  |  | 43 | $name2column21{$_}, | 
| 413 |  |  |  |  |  |  | ), keys %name2type; | 
| 414 | 6 |  |  |  |  | 45 | push @ast, map _type2updateinput($_), grep !$name2isview{$_}, keys %name2type; | 
| 415 |  |  |  |  |  |  | push @ast, { | 
| 416 |  |  |  |  |  |  | kind => 'type', | 
| 417 |  |  |  |  |  |  | name => 'Query', | 
| 418 |  |  |  |  |  |  | fields => { | 
| 419 |  |  |  |  |  |  | map { | 
| 420 | 6 |  |  |  |  | 36 | my $name = $_; | 
|  | 30 |  |  |  |  | 62 |  | 
| 421 | 30 |  |  |  |  | 58 | my $type = $name2type{$name}; | 
| 422 | 30 |  |  |  |  | 70 | my $pksearch_name = lcfirst $name; | 
| 423 | 30 |  |  |  |  | 103 | my $pksearch_name_plural = to_PL($pksearch_name); | 
| 424 | 30 |  |  |  |  | 27915 | my $input_search_name = "search$name"; | 
| 425 | 30 |  |  |  |  | 101 | my @fields = ( | 
| 426 |  |  |  |  |  |  | $input_search_name => _make_input_field($name, $name, 'search', 0, 1), | 
| 427 |  |  |  |  |  |  | ); | 
| 428 |  |  |  |  |  |  | push @fields, map(( | 
| 429 |  |  |  |  |  |  | ($_ ? $pksearch_name_plural : $pksearch_name), | 
| 430 |  |  |  |  |  |  | _make_query_pk_field($name, $type, \%name2pk21, $_), | 
| 431 | 30 | 100 |  |  |  | 66 | ), (0, 1)) if keys %{ $name2pk21{$name} }; | 
|  | 30 | 100 |  |  |  | 183 |  | 
| 432 | 30 |  |  |  |  | 190 | @fields; | 
| 433 |  |  |  |  |  |  | } keys %name2type | 
| 434 |  |  |  |  |  |  | }, | 
| 435 |  |  |  |  |  |  | }; | 
| 436 |  |  |  |  |  |  | push @ast, { | 
| 437 |  |  |  |  |  |  | kind => 'type', | 
| 438 |  |  |  |  |  |  | name => 'Mutation', | 
| 439 |  |  |  |  |  |  | fields => { | 
| 440 |  |  |  |  |  |  | map { | 
| 441 | 28 |  |  |  |  | 55 | my $name = $_; | 
| 442 | 28 |  |  |  |  | 63 | my $create_name = "create$name"; | 
| 443 | 28 |  |  |  |  | 57 | my $update_name = "update$name"; | 
| 444 | 28 |  |  |  |  | 93 | my $delete_name = "delete$name"; | 
| 445 |  |  |  |  |  |  | ( | 
| 446 | 28 |  |  |  |  | 83 | $create_name => _make_input_field($name, $name, 'create', 1, 1), | 
| 447 |  |  |  |  |  |  | $update_name => _make_input_field($name, $name, 'update', 1, 1), | 
| 448 |  |  |  |  |  |  | $delete_name => _make_input_field($name, 'Boolean', 'ID', 1, 1), | 
| 449 |  |  |  |  |  |  | ) | 
| 450 | 6 |  |  |  |  | 52 | } grep !$name2isview{$_}, keys %name2type | 
| 451 |  |  |  |  |  |  | }, | 
| 452 |  |  |  |  |  |  | }; | 
| 453 |  |  |  |  |  |  | +{ | 
| 454 | 6 |  |  |  |  | 136 | schema => GraphQL::Schema->from_ast(\@ast), | 
| 455 |  |  |  |  |  |  | root_value => $dbic_schema, | 
| 456 |  |  |  |  |  |  | resolver => \&field_resolver, | 
| 457 |  |  |  |  |  |  | }; | 
| 458 |  |  |  |  |  |  | } | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | =encoding utf-8 | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | =head1 NAME | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | GraphQL::Plugin::Convert::DBIC - convert DBIx::Class schema to GraphQL schema | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | =begin markdown | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | # PROJECT STATUS | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | | OS      |  Build status | | 
| 471 |  |  |  |  |  |  | |:-------:|--------------:| | 
| 472 |  |  |  |  |  |  | | Linux   | [](https://travis-ci.org/graphql-perl/GraphQL-Plugin-Convert-DBIC) | | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | [](https://metacpan.org/pod/GraphQL::Plugin::Convert::DBIC) | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | =end markdown | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | use GraphQL::Plugin::Convert::DBIC; | 
| 481 |  |  |  |  |  |  | use Schema; | 
| 482 |  |  |  |  |  |  | my $converted = GraphQL::Plugin::Convert::DBIC->to_graphql(Schema->connect); | 
| 483 |  |  |  |  |  |  | print $converted->{schema}->to_doc; | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | This module implements the L<GraphQL::Plugin::Convert> API to convert | 
| 488 |  |  |  |  |  |  | a L<DBIx::Class::Schema> to L<GraphQL::Schema> etc. | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | Its C<Query> type represents a guess at what fields are suitable, based | 
| 491 |  |  |  |  |  |  | on providing a lookup for each type (a L<DBIx::Class::ResultSource>). | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | =head2 Example | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | Consider this minimal data model: | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | blog: | 
| 498 |  |  |  |  |  |  | id # primary key | 
| 499 |  |  |  |  |  |  | articles # has_many | 
| 500 |  |  |  |  |  |  | title # non null | 
| 501 |  |  |  |  |  |  | language # nullable | 
| 502 |  |  |  |  |  |  | article: | 
| 503 |  |  |  |  |  |  | id # primary key | 
| 504 |  |  |  |  |  |  | blog # foreign key to Blog | 
| 505 |  |  |  |  |  |  | title # non null | 
| 506 |  |  |  |  |  |  | content # nullable | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | =head2 Generated Output Types | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | These L<GraphQL::Type::Object> types will be generated: | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | type Blog { | 
| 513 |  |  |  |  |  |  | id: Int! | 
| 514 |  |  |  |  |  |  | articles: [Article] | 
| 515 |  |  |  |  |  |  | title: String! | 
| 516 |  |  |  |  |  |  | language: String | 
| 517 |  |  |  |  |  |  | } | 
| 518 |  |  |  |  |  |  |  | 
| 519 |  |  |  |  |  |  | type Article { | 
| 520 |  |  |  |  |  |  | id: Int! | 
| 521 |  |  |  |  |  |  | blog: Blog | 
| 522 |  |  |  |  |  |  | title: String! | 
| 523 |  |  |  |  |  |  | content: String | 
| 524 |  |  |  |  |  |  | } | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | type Query { | 
| 527 |  |  |  |  |  |  | blog(id: [Int!]!): [Blog] | 
| 528 |  |  |  |  |  |  | article(id: [Int!]!): [Blog] | 
| 529 |  |  |  |  |  |  | } | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | Note that while the queries take a list, the return order is | 
| 532 |  |  |  |  |  |  | undefined. This also applies to the mutations. If this matters, request | 
| 533 |  |  |  |  |  |  | the primary key fields and use those to sort. | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | =head2 Generated Input Types | 
| 536 |  |  |  |  |  |  |  | 
| 537 |  |  |  |  |  |  | Different input types are needed for each of CRUD (Create, Read, Update, | 
| 538 |  |  |  |  |  |  | Delete). | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | The create one needs to have non-null fields be non-null, for idiomatic | 
| 541 |  |  |  |  |  |  | GraphQL-level error-catching. The read one needs all fields nullable, | 
| 542 |  |  |  |  |  |  | since this will be how searches are implemented, allowing fields to be | 
| 543 |  |  |  |  |  |  | left un-searched-for. Both need to omit primary key fields. The read | 
| 544 |  |  |  |  |  |  | one also needs to omit foreign key fields, since the idiomatic GraphQL | 
| 545 |  |  |  |  |  |  | way for this is to request the other object, with this as a field on it, | 
| 546 |  |  |  |  |  |  | then request any required fields of this. | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | Meanwhile, the update and delete ones need to include the primary key | 
| 549 |  |  |  |  |  |  | fields, to indicate what to mutate, and also all non-primary key fields | 
| 550 |  |  |  |  |  |  | as nullable, which for update will mean leaving them unchanged, and for | 
| 551 |  |  |  |  |  |  | delete is to be ignored. These input types are split into one input | 
| 552 |  |  |  |  |  |  | for the primary keys, which is a full input type to allow for multiple | 
| 553 |  |  |  |  |  |  | primary keys, then a wrapper input for updates, that takes one ID input, | 
| 554 |  |  |  |  |  |  | and a payload that due to the same requirements, is just the search input. | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | Therefore, for the above, these input types (and an updated Query, | 
| 557 |  |  |  |  |  |  | and Mutation) are created: | 
| 558 |  |  |  |  |  |  |  | 
| 559 |  |  |  |  |  |  | input BlogCreateInput { | 
| 560 |  |  |  |  |  |  | title: String! | 
| 561 |  |  |  |  |  |  | language: String | 
| 562 |  |  |  |  |  |  | } | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | input BlogSearchInput { | 
| 565 |  |  |  |  |  |  | title: String | 
| 566 |  |  |  |  |  |  | language: String | 
| 567 |  |  |  |  |  |  | } | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | input BlogIDInput { | 
| 570 |  |  |  |  |  |  | id: Int! | 
| 571 |  |  |  |  |  |  | } | 
| 572 |  |  |  |  |  |  |  | 
| 573 |  |  |  |  |  |  | input BlogUpdateInput { | 
| 574 |  |  |  |  |  |  | id: BlogIDInput! | 
| 575 |  |  |  |  |  |  | payload: BlogSearchInput! | 
| 576 |  |  |  |  |  |  | } | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | input ArticleCreateInput { | 
| 579 |  |  |  |  |  |  | blog_id: Int! | 
| 580 |  |  |  |  |  |  | title: String! | 
| 581 |  |  |  |  |  |  | content: String | 
| 582 |  |  |  |  |  |  | } | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | input ArticleSearchInput { | 
| 585 |  |  |  |  |  |  | title: String | 
| 586 |  |  |  |  |  |  | content: String | 
| 587 |  |  |  |  |  |  | } | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | input ArticleIDInput { | 
| 590 |  |  |  |  |  |  | id: Int! | 
| 591 |  |  |  |  |  |  | } | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | input ArticleUpdateInput { | 
| 594 |  |  |  |  |  |  | id: ArticleIDInput! | 
| 595 |  |  |  |  |  |  | payload: ArticleSearchInput! | 
| 596 |  |  |  |  |  |  | } | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | type Mutation { | 
| 599 |  |  |  |  |  |  | createBlog(input: [BlogCreateInput!]!): [Blog] | 
| 600 |  |  |  |  |  |  | createArticle(input: [ArticleCreateInput!]!): [Article] | 
| 601 |  |  |  |  |  |  | deleteBlog(input: [BlogIDInput!]!): [Boolean] | 
| 602 |  |  |  |  |  |  | deleteArticle(input: [ArticleIDInput!]!): [Boolean] | 
| 603 |  |  |  |  |  |  | updateBlog(input: [BlogUpdateInput!]!): [Blog] | 
| 604 |  |  |  |  |  |  | updateArticle(input: [ArticleUpdateInput!]!): [Article] | 
| 605 |  |  |  |  |  |  | } | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | extends type Query { | 
| 608 |  |  |  |  |  |  | searchBlog(input: BlogSearchInput!): [Blog] | 
| 609 |  |  |  |  |  |  | searchArticle(input: ArticleSearchInput!): [Article] | 
| 610 |  |  |  |  |  |  | } | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | =head1 ARGUMENTS | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | To the C<to_graphql> method: a  L<DBIx::Class::Schema> object. | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | =head1 PACKAGE FUNCTIONS | 
| 617 |  |  |  |  |  |  |  | 
| 618 |  |  |  |  |  |  | =head2 field_resolver | 
| 619 |  |  |  |  |  |  |  | 
| 620 |  |  |  |  |  |  | This is available as C<\&GraphQL::Plugin::Convert::DBIC::field_resolver> | 
| 621 |  |  |  |  |  |  | in case it is wanted for use outside of the "bundle" of the C<to_graphql> | 
| 622 |  |  |  |  |  |  | method. | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | =head1 DEBUGGING | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | To debug, set environment variable C<GRAPHQL_DEBUG> to a true value. | 
| 627 |  |  |  |  |  |  |  | 
| 628 |  |  |  |  |  |  | =head1 AUTHOR | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  | Ed J, C<< <etj at cpan.org> >> | 
| 631 |  |  |  |  |  |  |  | 
| 632 |  |  |  |  |  |  | =head1 LICENSE | 
| 633 |  |  |  |  |  |  |  | 
| 634 |  |  |  |  |  |  | Copyright (C) Ed J | 
| 635 |  |  |  |  |  |  |  | 
| 636 |  |  |  |  |  |  | This library is free software; you can redistribute it and/or modify | 
| 637 |  |  |  |  |  |  | it under the same terms as Perl itself. | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | =cut | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  | 1; |