Skip to content

Feature/persistent term #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ language: erlang
sudo: false

os: linux
otp_release: 19.3
otp_release: 21.2

matrix:
include:
Expand All @@ -12,6 +12,8 @@ matrix:
otp_release: 20.3
- os: linux
otp_release: 21.1
- os: linux
otp_release: 21.2

script: "make compile test dialyzer REBAR=./rebar3"

Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ the compatibility issues you are likely to encounter.

## [Unreleased]

### Compatibility

* On OTP 21, we now assume the presence of `persistent_term` as in
Erlang/OTP 21.2. A new call `graphql:populate_persistent_table/0`
exists to populate this table for faster execution. It will be
automatically called on `graphql:validate_schema/0` as well, so if
you are using the (recommended) schema method of creating GraphQL
contracts, the system will now utilize the new feature. This also
means faster execution and less schema copying.

### Added

* Add proper support for OTP release 21 (by getong, 18年梦醒). Detect the
Expand All @@ -33,7 +43,6 @@ the compatibility issues you are likely to encounter.
errors. These are not allowed per the Jun2018 specification and
clarification.


## [0.15.0] 2018-12-02 Hex.pm release

### Compatiblity
Expand Down
22 changes: 21 additions & 1 deletion src/graphql.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
-export([
load_schema/2,
insert_schema_definition/1,
validate_schema/0
validate_schema/0,
populate_persistent_table/0
]).

%% Internal
Expand Down Expand Up @@ -164,5 +165,24 @@ insert_root(Defn) ->

%% STUB for now
-spec validate_schema() -> ok | {error, any()}.

-ifdef(HAVE_PERSISTENT_TERM).
validate_schema() ->
case graphql_schema:populate_persistent_table() of
ok ->
ignore;
{error, Reason} ->
error_logger:info_report([warning, {populate_persistent_table, Reason}])
end,
graphql_schema_validate:x().
-else.
validate_schema() ->
ok = graphql_schema:populate_persistent_table(),
graphql_schema_validate:x().
-endif.

populate_persistent_table() ->
graphql_schema:populate_persistent_table().



2 changes: 1 addition & 1 deletion src/graphql_check.erl
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ infer_type({list, Ty}) ->
{Polarity, V} -> {Polarity, {list, V}}
end;
infer_type({scalar, Name}) ->
#scalar_type{} = Ty = graphql_schema:get(Name),
#scalar_type{} = Ty = graphql_schema:lookup(Name),
{_polarity, Ty} = infer_type(Ty);
%% NonPolar
infer_type(#scalar_type{} = Ty) -> {'*', Ty};
Expand Down
18 changes: 13 additions & 5 deletions src/graphql_execute.erl
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,12 @@ complete_value(Ctx, Ty, Fields, {ok, Value}) when is_binary(Ty) ->
error_logger:warning_msg(
"Canary: Type lookup during value completion for: ~p~n",
[Ty]),
SchemaType = graphql_schema:get(Ty),
complete_value(Ctx, SchemaType, Fields, {ok, Value});
case graphql_schema:lookup(Ty) of
not_found ->
exit(schema_lookup_failed);
SchemaType ->
complete_value(Ctx, SchemaType, Fields, {ok, Value})
end;
complete_value(#ectx{ defer_target = Upstream } = Ctx,
{non_null, InnerTy}, Fields, Result) ->
%% Note we handle arbitrary results in this case. This makes sure errors
Expand Down Expand Up @@ -620,7 +624,7 @@ resolve_abstract_type(Module, Value) when is_atom(Module) ->
resolve_abstract_type(Resolver, Value) when is_function(Resolver, 1) ->
try Resolver(Value) of
{ok, Ty} ->
Obj = #object_type{} = graphql_schema:get(binarize(Ty)),
Obj = #object_type{} = graphql_schema:lookup(binarize(Ty)),
{ok, Obj};
{error, Reason} ->
{error, {type_resolver_error, Reason}}
Expand Down Expand Up @@ -971,8 +975,12 @@ value(Ctx, Ty, Val) ->
#enum_type {} ->
Val;
Bin when is_binary(Bin) ->
LoadedTy = graphql_schema:get(Bin),
value(Ctx, LoadedTy, Val)
case graphql_schema:lookup(Bin) of
not_found ->
exit(schema_lookup_failed);
LoadedTy ->
value(Ctx, LoadedTy, Val)
end
end.

value_object(_, _, []) -> [];
Expand Down
12 changes: 8 additions & 4 deletions src/graphql_internal.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,13 @@
locations = [] :: [atom()]}).

-ifdef(OTP_RELEASE). %% this implies 21 or higher
-define(EXCEPTION(Class, Reason, Stacktrace), Class:Reason:Stacktrace).
-define(GET_STACK(Stacktrace), Stacktrace).
-if(?OTP_RELEASE >= 21).
-define(HAVE_PERSISTENT_TERM, true).

-define(EXCEPTION(Class, Reason, Stacktrace), Class:Reason:Stacktrace).
-define(GET_STACK(Stacktrace), Stacktrace).
-endif.
-else.
-define(EXCEPTION(Class, Reason, _), Class:Reason).
-define(GET_STACK(_), erlang:get_stacktrace()).
-define(EXCEPTION(Class, Reason, _), Class:Reason).
-define(GET_STACK(_), erlang:get_stacktrace()).
-endif.
12 changes: 6 additions & 6 deletions src/graphql_introspection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
when QueryObj :: binary().

augment_root(QName) ->
#object_type{ fields = Fields } = Obj = graphql_schema:get(QName),
#object_type{ fields = Fields } = Obj = graphql_schema:lookup_ets(QName),
Schema = #schema_field {
ty = {non_null, <<"__Schema">>},
description = <<"The introspection schema">>,
Expand Down Expand Up @@ -49,24 +49,24 @@ schema_resolver(_Ctx, none, _, #{}) ->
directive(skip)]}}.

type_resolver(_Ctx, none, _, #{ <<"name">> := N }) ->
case graphql_schema:lookup(N) of
case graphql_schema:lookup_ets(N) of
not_found -> {ok, null};
Ty -> render_type(Ty)
end.

query_type(_Ctx, _Obj, _, _) ->
#root_schema{ query = QType } = graphql_schema:get('ROOT'),
#root_schema{ query = QType } = graphql_schema:lookup_ets('ROOT'),
render_type(QType).

mutation_type(_Ctx, _Obj, _, _) ->
#root_schema { mutation = MType } = graphql_schema:get('ROOT'),
#root_schema { mutation = MType } = graphql_schema:lookup_ets('ROOT'),
case MType of
undefined -> {ok, null};
MT -> render_type(MT)
end.

subscription_type(_Ctx, _Obj, _, _) ->
#root_schema { subscription = SType } = graphql_schema:get('ROOT'),
#root_schema { subscription = SType } = graphql_schema:lookup_ets('ROOT'),
case SType of
undefined -> {ok, null};
ST -> render_type(ST)
Expand All @@ -83,7 +83,7 @@ schema_types(_Ctx, _Obj, _, _Args) ->

%% Main renderer. Calls out to the subsets needed
render_type(Name) when is_binary(Name) ->
case graphql_schema:lookup(Name) of
case graphql_schema:lookup_ets(Name) of
not_found ->
throw({not_found, Name});
Ty -> render_type(Ty)
Expand Down
53 changes: 42 additions & 11 deletions src/graphql_schema.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,26 @@
-include("graphql_schema.hrl").
-include("graphql_internal.hrl").

-export([start_link/0, reset/0]).
-export([start_link/0, reset/0, populate_persistent_table/0]).
-export([
all/0,
insert/1, insert/2,
load/1, load_schema/1,
get/1,
lookup/1,
lookup/1, lookup_ets/1,
validate_enum/2,
lookup_interface_implementors/1
]).
-export([resolve_root_type/2]).

-export([id/1]).



%% Callbacks
-export([init/1, handle_call/3, handle_cast/2, terminate/2, handle_info/2,
code_change/3]).

-define(PTERM_KEY, {?MODULE, objects}).
-define(ENUMS, graphql_schema_enums). % Table mapping enum values of type binary() to set of enum type ids
-define(OBJECTS, graphql_schema_objects). % Table of all types

Expand All @@ -43,6 +45,17 @@ reset() ->
ok = graphql_builtins:standard_directives_inject(),
ok.


-ifdef(HAVE_PERSISTENT_TERM).
-spec populate_persistent_table() -> ok | {error, Reason :: term()}.
populate_persistent_table() ->
gen_server:call(?MODULE, populate_persistent_table).
-else.
-spec populate_persistent_table() -> ok.
populate_persistent_table() ->
ok.
-endif.

-spec insert(any()) -> ok.
insert(S) ->
insert(S, #{ canonicalize => true }).
Expand Down Expand Up @@ -98,13 +111,6 @@ insert_new_(Rec) ->
all() ->
ets:match_object(?OBJECTS, '_').

-spec get(binary() | 'ROOT') -> schema_object().
get(ID) ->
case ets:lookup(?OBJECTS, ID) of
[S] -> S;
_ -> exit(schema_not_found)
end.

%% Check if given enum value matches the given type id, other enums,
%% or nothing at all.
-spec validate_enum(binary(), binary()) -> ok | not_found | {other_enums, [#enum_type{}]}.
Expand All @@ -113,7 +119,7 @@ validate_enum(EnumID, EnumValue) ->
#{EnumID := _} -> ok;
EnumIDsMap ->
EnumIDs = maps:keys(EnumIDsMap),
OtherEnums = [?MODULE:get(ID) || ID <- EnumIDs],
OtherEnums = [?MODULE:lookup(ID) || ID <- EnumIDs],
{other_enums, OtherEnums}
catch
error:badarg ->
Expand All @@ -135,11 +141,26 @@ lookup_interface_implementors(IFaceID) ->
qlc:e(QH).

-spec lookup(binary() | 'ROOT') -> schema_object() | not_found.
-ifdef(HAVE_PERSISTENT_TERM).
lookup(ID) ->
case erlang:function_exported(persistent_term, get, 1) of
true ->
Map = persistent_term:get(?PTERM_KEY),
maps:get(ID, Map, not_found);
false ->
lookup_ets(ID)
end.
-else.
lookup(ID) ->
lookup_ets(ID).
-endif.

lookup_ets(ID) ->
case ets:lookup(?OBJECTS, ID) of
[S] -> S;
_ -> not_found
end.


-spec resolve_root_type(undefined | operation_type(), root_schema()) -> undefined | binary().
resolve_root_type(undefined, #root_schema { query = Q }) -> Q;
Expand Down Expand Up @@ -176,6 +197,16 @@ handle_cast(_Msg, State) -> {noreply, State}.
when
S :: #state{},
M :: term().
handle_call(populate_persistent_table, _From, State) ->
Objects = all(),
Map = maps:from_list([{id(O), O} || O <- Objects]),
try persistent_term:put(?PTERM_KEY, Map) of
ok ->
{reply, ok, State}
catch
error:_ ->
{reply, {error, no_persistent_table_support}, State}
end;
handle_call({insert, X}, _From, State) ->
case determine_table(X) of
{error, unknown} ->
Expand Down
2 changes: 1 addition & 1 deletion src/graphql_schema_parse.erl
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ handle_type({non_null, T}) -> {non_null, handle_type(T)};
handle_type({list, T}) -> {list, handle_type(T)};
handle_type({name, _, T}) -> binary_to_atom(T, utf8);
handle_type({scalar, Name}) ->
#scalar_type{} = Ty = graphql_schema:get(Name),
#scalar_type{} = Ty = graphql_schema:lookup_ets(Name),
handle_type(Ty);
handle_type(#scalar_type{ id = Id }) -> binary_to_atom(Id, utf8).

Expand Down
4 changes: 2 additions & 2 deletions src/graphql_schema_validate.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ root_lookup(Val, Type) ->
%% - Directly written Mutations and Subscriptions MUST exist
%% - Try to coerce otherwise
root_lookup_(Q, Type, Def) ->
case graphql_schema:lookup(Q) of
case graphql_schema:lookup_ets(Q) of
not_found when Type == query -> err({schema_without_query, Q});
not_found when Def == direct -> err({schema_missing_type, Q});
not_found -> {ok, undefined};
Expand Down Expand Up @@ -269,7 +269,7 @@ all(F, [E|Es]) ->
all(F, Es).

lookup(Key) ->
case graphql_schema:lookup(Key) of
case graphql_schema:lookup_ets(Key) of
not_found -> err({not_found, Key});
X -> X
end.
Expand Down