File Coverage

blib/lib/SQL/Translator/Producer/GraphQL.pm
Criterion Covered Total %
statement 135 135 100.0
branch 10 12 83.3
condition 14 16 87.5
subroutine 28 28 100.0
pod 0 2 0.0
total 187 193 96.8


line stmt bran cond sub pod time code
1             package SQL::Translator::Producer::GraphQL;
2 2     2   9816 use 5.008001;
  2         7  
3 2     2   10 use strict;
  2         2  
  2         34  
4 2     2   9 use warnings;
  2         2  
  2         41  
5 2     2   1151 use SQL::Translator::Producer::DBIx::Class::File;
  2         16586  
  2         62  
6 2     2   849 use GraphQL::Schema;
  2         2240657  
  2         1888  
7              
8             our $VERSION = "0.01";
9              
10             my %TYPEMAP = (
11             guid => 'String',
12             wlongvarchar => 'String',
13             wvarchar => 'String',
14             wchar => 'String',
15             bigint => 'Int',
16             bit => 'Int',
17             tinyint => 'Int',
18             longvarbinary => 'String',
19             varbinary => 'String',
20             binary => 'String',
21             longvarchar => 'String',
22             unknown_type => 'String',
23             all_types => 'String',
24             char => 'String',
25             numeric => 'Float',
26             decimal => 'Float',
27             integer => 'Int',
28             smallint => 'Int',
29             float => 'Float',
30             real => 'Float',
31             double => 'Float',
32             datetime => 'DateTime',
33             date => 'DateTime',
34             interval => 'Int',
35             time => 'DateTime',
36             timestamp => 'DateTime',
37             varchar => 'String',
38             boolean => 'Boolean',
39             udt => 'String',
40             udt_locator => 'String',
41             row => 'String',
42             ref => 'String',
43             blob => 'String',
44             blob_locator => 'String',
45             clob => 'String',
46             clob_locator => 'String',
47             array => 'String',
48             array_locator => 'String',
49             multiset => 'String',
50             multiset_locator => 'String',
51             type_date => 'DateTime',
52             type_time => 'DateTime',
53             type_timestamp => 'DateTime',
54             type_time_with_timezone => 'DateTime',
55             type_timestamp_with_timezone => 'DateTime',
56             interval_year => 'Int',
57             interval_month => 'Int',
58             interval_day => 'Int',
59             interval_hour => 'Int',
60             interval_minute => 'Int',
61             interval_second => 'Int',
62             interval_year_to_month => 'Int',
63             interval_day_to_hour => 'Int',
64             interval_day_to_minute => 'Int',
65             interval_day_to_second => 'Int',
66             interval_hour_to_minute => 'Int',
67             interval_hour_to_second => 'Int',
68             interval_minute_to_second => 'Int',
69             # not DBI SQL_* types
70             int => 'Int',
71             text => 'String',
72             );
73              
74             sub _dbicsource2pretty {
75 8     8   23 my ($source) = @_;
76 8   66     211 $source = $source->source_name || $source;
77 8         821 $source =~ s#.*::##;
78 8         56 join '', map ucfirst, split /_+/, $source;
79             }
80              
81             sub _apply_modifier {
82 52     52   118 my ($modifier, $typespec) = @_;
83 52 100       122 return $typespec if !$modifier;
84 42 100 100     190 return $typespec if $modifier eq 'non_null'
      66        
85             and ref $typespec eq 'ARRAY'
86             and $typespec->[0] eq 'non_null'; # no double-non_null
87 34         174 [ $modifier, { type => $typespec } ];
88             }
89              
90             sub _type2input {
91 4     4   15 my ($name, $fields, $pk21, $fk21) = @_;
92             +{
93             kind => 'input',
94             name => "${name}Input",
95             fields => {
96 8         36 map { ($_ => $fields->{$_}) }
97 4   100     46 grep !$pk21->{$_} && !$fk21->{$_}, keys %$fields
98             },
99             };
100             }
101              
102             sub schema_dbic2graphql {
103 2     2 0 63263 my ($dbic_schema) = @_;
104 2         19 my @ast = ({kind => 'scalar', name => 'DateTime'});
105 2         10 my (%name2type, %name2columns, %name2pk21, %name2fk21);
106 2         16 for my $source (map $dbic_schema->source($_), $dbic_schema->sources) {
107 4         285 my $name = _dbicsource2pretty($source);
108 4         12 my %fields;
109 4         21 my $columns_info = $source->columns_info;
110 4         151 $name2pk21{$name} = +{ map { ($_ => 1) } $source->primary_columns };
  4         46  
111             my %rel2info = map {
112 4         22 ($_ => $source->relationship_info($_))
  4         35  
113             } $source->relationships;
114 4         33 for my $column (keys %$columns_info) {
115 14         27 my $info = $columns_info->{$column};
116             $fields{$column} = +{
117             type => _apply_modifier(
118             !$info->{is_nullable} && 'non_null',
119             $TYPEMAP{ lc $info->{data_type} },
120 14   100     67 ),
121             };
122 14 100       37 $name2fk21{$name}->{$column} = 1 if $info->{is_foreign_key};
123 14         21 push @{ $name2columns{$name} }, $column;
  14         35  
124             }
125 4         20 push @ast, _type2input($name, \%fields, $name2pk21{$name}, $name2fk21{$name});
126 4         15 for my $rel (keys %rel2info) {
127 4         9 my $info = $rel2info{$rel};
128 4         13 my $type = _dbicsource2pretty($info->{source});
129 4         14 $rel =~ s/_id$//; # dumb heuristic
130 4 50       9 $rel .= '1' if grep $_ eq $rel, @{ $name2columns{$name} };
  4         20  
131 4 100       32 $type = _apply_modifier('list', $type) if $info->{attrs}{accessor} eq 'multi';
132 4         17 $fields{$rel} = +{
133             type => $type,
134             };
135             }
136 4         19 my $spec = +{
137             kind => 'type',
138             name => $name,
139             fields => \%fields,
140             };
141 4         10 $name2type{$name} = $spec;
142 4         21 push @ast, $spec;
143             }
144             push @ast, {
145             kind => 'type',
146             name => 'Query',
147             fields => {
148             map {
149 2         7 my $name = $_;
  4         9  
150 4         10 my $type = $name2type{$name};
151             map {
152             (lc($name).'By'.ucfirst($_) => {
153             type => _apply_modifier(!$name2pk21{$name}->{$_} && 'list', $name),
154             args => {
155 14   100     79 $_ => { type => _apply_modifier('non_null', $type->{fields}{$_}{type}) }
156             },
157             })
158 4         8 } @{$name2columns{$name}}
  4         9  
159             } keys %name2type
160             },
161             };
162             push @ast, {
163             kind => 'type',
164             name => 'Mutation',
165             fields => {
166             map {
167 2         17 my $name = $_;
  4         11  
168 4         10 my $type = $name2type{$name};
169             (
170             "create$name" => {
171             type => $name,
172             args => {
173             input => { type => _apply_modifier('non_null', "${name}Input") },
174             (map {
175             $_ => { type => $type->{fields}{$_}{type} }
176 2         20 } keys %{ $name2fk21{$name} }),
  4         21  
177             },
178             },
179             "update$name" => {
180             type => $name,
181             args => {
182             input => { type => _apply_modifier('non_null', "${name}Input") },
183             (map {
184             $_ => { type => $type->{fields}{$_}{type} }
185 6         39 } keys %{ $name2pk21{$name} }, keys %{ $name2fk21{$name} }),
  4         17  
  4         13  
186             },
187             },
188             "delete$name" => {
189             type => 'Boolean',
190             args => {
191             (map {
192             $_ => { type => $type->{fields}{$_}{type} }
193 4         19 } keys %{ $name2pk21{$name} }),
  4         38  
  4         10  
194             },
195             },
196             )
197             } keys %name2type
198             },
199             };
200 2         24 GraphQL::Schema->from_ast(\@ast);
201             }
202              
203             my $dbic_schema_class_track = 'CLASS00000';
204             sub produce {
205 2     2 0 1146432 my $translator = shift;
206 2         35 my $schema = $translator->schema;
207 2         76 my $dbic_schema_class = ++$dbic_schema_class_track;
208 2         31 my $dbic_translator = bless { %$translator }, ref $translator;
209 2         44 $dbic_translator->producer_args({ prefix => $dbic_schema_class });
210 2     1   100 eval SQL::Translator::Producer::DBIx::Class::File::produce($dbic_translator);
  1     1   17568  
  1     1   3  
  1     1   1255  
  1     1   30817  
  1     1   27  
  1     1   20  
  1     1   5  
  1     1   2  
  1     1   130  
  1     1   7  
  1     1   2  
  1     1   101  
  1     1   24  
  1     1   3  
  1     1   33  
  1     1   10  
  1     1   3  
  1         136  
  1         7  
  1         2  
  1         1844  
  1         7901  
  1         3  
  1         18  
  1         5  
  1         2  
  1         37  
  1         22165  
  1         2  
  1         169  
  1         8  
  1         2  
  1         28  
  1         5  
  1         3  
  1         167  
  1         8  
  1         3  
  1         82  
  1         6  
  1         3  
  1         18  
  1         5  
  1         3  
  1         123  
  1         6  
  1         2  
  1         85  
  1         8  
  1         3  
  1         22  
  1         8  
  1         5  
  1         63  
211 2 50       12 die "Failed to make DBIx::Class::Schema: $@" if $@;
212 2         19 my $graphql_schema = schema_dbic2graphql($dbic_schema_class->connect);
213 2         248663 $graphql_schema->to_doc;
214             }
215              
216             =encoding utf-8
217              
218             =head1 NAME
219              
220             SQL::Translator::Producer::GraphQL - GraphQL schema producer for SQL::Translator
221              
222             =begin markdown
223              
224             # PROJECT STATUS
225              
226             | OS | Build status |
227             |:-------:|--------------:|
228             | Linux | [![Build Status](https://travis-ci.org/graphql-perl/SQL-Translator-Producer-GraphQL.svg?branch=master)](https://travis-ci.org/graphql-perl/SQL-Translator-Producer-GraphQL) |
229              
230             [![CPAN version](https://badge.fury.io/pl/SQL::Translator::Producer::GraphQL.svg)](https://metacpan.org/pod/SQL::Translator::Producer::GraphQL)
231              
232             =end markdown
233              
234             =head1 SYNOPSIS
235              
236             use SQL::Translator;
237             use SQL::Translator::Producer::GraphQL;
238             my $t = SQL::Translator->new( parser => '...' );
239             $t->producer('GraphQL');
240             $t->translate;
241              
242             =head1 DESCRIPTION
243              
244             This module will produce a L from the given
245             L. It does this by first
246             turning it into a L using
247             L, and introspecting it.
248              
249             Its C type represents a guess at what fields are suitable, based
250             on providing a lookup for each type (a L)
251             by each of its columns.
252              
253             The C type is similar: one C per
254             "real" type.
255              
256             =head1 ARGUMENTS
257              
258             Currently none.
259              
260             =head1 AUTHOR
261              
262             Ed J, C<< >>
263              
264             Based heavily on L.
265              
266             =head1 LICENSE
267              
268             Copyright (C) Ed J
269              
270             This library is free software; you can redistribute it and/or modify
271             it under the same terms as Perl itself.
272              
273             =cut
274              
275             1;