diff --git a/include/types.hrl b/include/types.hrl index fe2911c..233ffc7 100644 --- a/include/types.hrl +++ b/include/types.hrl @@ -43,13 +43,13 @@ -record(many_to_one,{ local_id :: field_name(), - model :: model_name(), + remote_model :: model_name(), remote_id :: field_name() }). -record(one_to_many,{ local_id :: field_name(), - model :: model_name(), + remote_model :: model_name(), remote_id :: field_name() }). diff --git a/priv/templates/crud.dtl b/priv/templates/crud.dtl index 26e30df..fadf4ec 100644 --- a/priv/templates/crud.dtl +++ b/priv/templates/crud.dtl @@ -22,14 +22,14 @@ get( %\ {% endfor %} ) -> dip_orm( - select({{model_name}}), + select({{model}}), where( %| {% for index_field in index_fields %} %| {% if not forloop.first %} %\ - , + andalso %| {% endif %} - {{index_field.name}} = {{index_field.name|capfirst}} + {{index_field.name}} == {{index_field.name|capfirst}} %\ {% endfor %} )). @@ -46,20 +46,20 @@ save({{model_name|capfirst}}) -> ]). save({{model_name|capfirst}},ChangedFields,true) -> dip_orm( - insert({{model_name}}), + insert({{model}}), values(ChangedFields) ); save({{model_name|capfirst}},ChangedFields,field) -> dip_orm( - update({{model_name}}), + update({{model}}), values(ChangedFields), where( %| {% for index_field in index_fields %} %| {% if not forloop.first %} %\ - , + andalso %| {% endif %} - {{index_field.name}} = {{model_name|capfirst}}#{{model_name}}.{{index_field.name}} + {{index_field.name}} == {{model_name|capfirst}}#{{model_name}}.{{index_field.name}} %\ {% endfor %} )). @@ -69,14 +69,14 @@ save({{model_name|capfirst}},ChangedFields,field) -> Reason :: db_error. delete({{model_name|capfirst}}) -> dip_orm( - delete({{model_name}}), + delete({{model}}), where( %| {% for index_field in index_fields %} %| {% if not forloop.first %} %\ - , + andalso %| {% endif %} - {{index_field.name}} = {{model_name|capfirst}}#{{model_name}}.{{index_field.name}} + {{index_field.name}} == {{model_name|capfirst}}#{{model_name}}.{{index_field.name}} %\ {% endfor %} )). diff --git a/src/dip_orm_compiler.erl b/src/dip_orm_compiler.erl index 14f89b2..23bd041 100644 --- a/src/dip_orm_compiler.erl +++ b/src/dip_orm_compiler.erl @@ -43,7 +43,7 @@ models(RebarConfig,_AppFile) -> RawDBModelConfigs), DBModelConfigs2 <- dip_orm_configs:fill_links(DBModelConfigs), write_db_models(DBModelConfigs2,GlobalConfig), - dip_orm_config_file:write(dip_orm_config,DBModelConfigs2,GlobalConfig) + dip_orm_config_file:write(dip_orm,DBModelConfigs2,GlobalConfig) ]). %% =================================================================== diff --git a/src/dip_orm_configs.erl b/src/dip_orm_configs.erl index 0907251..0dd9ed2 100644 --- a/src/dip_orm_configs.erl +++ b/src/dip_orm_configs.erl @@ -17,6 +17,16 @@ get_db_model_config/1, fill_links/1 ]). + +-export([ + find_model/2, + get_model_field/2, + + field/2, + model/2, + + find_link/2 + ]). -include("log.hrl"). %% =================================================================== @@ -39,6 +49,65 @@ %%% API %% =================================================================== +find_model(ModelName,Models) -> + do([error_m || + ModelName2 <- not_null_binary(ModelName), + find_model_(ModelName2,Models) + ]). + +find_model_(ModelName,Models) -> + case lists:keyfind(ModelName,#config.name,Models) of + #config{} = Model -> + {ok,Model}; + false -> + Reason = dip_utils:template("Unknown model: ~s",[ModelName]), + {error,Reason} + end. + +find_link(#config{name=LocalModelName,links=Links}, + #config{name=RemoteModelName}) -> + FoldFun = fun(#one_to_many{remote_model=R}=Link,_Acc) when R =:= RemoteModelName-> + {ok,Link}; + (#many_to_one{remote_model=R}=Link,_Acc) when R =:= RemoteModelName -> + {ok,Link}; + (#one_to_many{remote_model=R}=Link,_Acc) when R =:= RemoteModelName -> + {ok,Link}; + (_,Acc) -> + Acc + end, + Reason = dip_utils:template("No model '~s' is linked to '~s'",[RemoteModelName,LocalModelName]), + lists:foldl(FoldFun,{error,Reason},Links). + + +get_model_field(FieldName,ModelConfig) -> + do([error_m || + FieldName2 <- not_null_binary(FieldName), + get_model_field_(FieldName2,ModelConfig) + ]). +get_model_field_(FieldName,#config{fields=Fields}) -> + case lists:keyfind(FieldName,#field.name,Fields) of + #field{is_in_database=true} = Field -> + {ok,Field}; + #field{is_in_database=false} -> + Reason = dip_utils:template("Field '~s' does not stores in database",[FieldName]), + {error,Reason}; + false -> + Reason = dip_utils:template("Unknown field: ~s",[FieldName]), + {error,Reason} + end. + +field(name,#field{name=Name}) -> Name; +field(db_type,#field{db_options=#db_options{type=Db_type}}) -> Db_type. + +model(name,#config{name=Name}) -> Name; +model(db_fields,#config{fields=Fields}) -> + [F || F <- Fields,F#field.is_in_database,((F#field.record_options)#record_options.mode)#access_mode.sr]. + + +%% =================================================================== +%%% API +%% =================================================================== + get_global_config(Config) -> do([error_m || Opts <- default(option,dip_orm_options,Config,[]), @@ -110,7 +179,7 @@ fill_links(ModelConfigs) -> fill_one_to_many_links(ModelConfigs,Dict) -> FoldFun = fun(#config{name=LocalModelName,fields=LocalFields,links=LocalLinks},Acc) -> SubFoldFun = fun(#many_to_one{local_id=LocalID, - model=RemoteModelName, + remote_model=RemoteModelName, remote_id=RemoteID}, Acc2) -> do([error_m || @@ -120,7 +189,7 @@ fill_one_to_many_links(ModelConfigs,Dict) -> check_fields(LocalField,RemoteField), Link <- return(#one_to_many{ local_id=RemoteID, - model=LocalModelName, + remote_model=LocalModelName, remote_id=LocalID }), return(dict:store(RemoteModelName,{RemoteFields,[Link|RemoteLinks]},Acc2)) @@ -162,10 +231,10 @@ get_many_to_many_links(#config{name=ModelName,links=Links}) -> get_many_to_many_links_(_LocalModelName,[],Acc) -> Acc; get_many_to_many_links_(LinkModelName,[#many_to_one{local_id=LinkID_1, - model=RemoteModelName_1, + remote_model=RemoteModelName_1, remote_id=RemoteID_1}|RestLinks],Acc) -> FoldFun = fun(#many_to_one{local_id=LinkID_2, - model=RemoteModelName_2, + remote_model=RemoteModelName_2, remote_id=RemoteID_2},FoldAcc) -> [{RemoteModelName_1,#many_to_many{local_id = RemoteID_1, link_model = LinkModelName, @@ -414,7 +483,7 @@ find_links(Fields) -> }}, Acc) -> Link = #many_to_one{ local_id=Name, - model=Model, + remote_model=Model, remote_id=Field }, [Link|Acc]; diff --git a/src/dip_orm_file.erl b/src/dip_orm_file.erl index 6d1756d..f3a5a1d 100644 --- a/src/dip_orm_file.erl +++ b/src/dip_orm_file.erl @@ -8,6 +8,8 @@ -module(dip_orm_file). -compile({parse_transform,do}). +-include("log.hrl"). + -export([extract_config/1, get_module_name/1, place_generated_block/2, @@ -30,7 +32,7 @@ read_config(Filename) -> case file:consult(Filename) of {ok,Config} -> {ok,Config}; {error,Reason} -> - {error,{wrong_syntax,Reason}} + {error,{Filename,Reason}} end. diff --git a/src/dip_orm_parse_transform.erl b/src/dip_orm_parse_transform.erl new file mode 100644 index 0000000..3c967bb --- /dev/null +++ b/src/dip_orm_parse_transform.erl @@ -0,0 +1,317 @@ +%%%------------------------------------------------------------------- +%%% @author egobrain +%%% @copyright (C) 2012, egobrain +%%% @doc +%%% +%%% @end +%%% Created : 3 Dec 2012 by egobrain +%%%------------------------------------------------------------------- +-module(dip_orm_parse_transform). + +-export([parse_transform/3]). + +-include("log.hrl"). +-include("types.hrl"). + +-compile({parse_transform,do}). +-compile({parse_transform,cut}). + +%% =================================================================== +%%% Types +%% =================================================================== + +% -record(config,{ +% models +% }). +-record(request,{ + type :: select | insert | update | delete, + model_to_select :: dip_orm_config:config(), + configs = [dip_orm_config:config()], + where, + links = [] + }). + +%% =================================================================== +%%% Api +%% =================================================================== + +% TODO: сообщения об ошибках в декораторе +parse_transform(Ast,_Options,Config)-> + % ?DBG("~p~n=======~n",[Ast]), + % ?DBG("~s~n=======~n",[pretty_print(Ast)]), + Res = case transform_ast(Ast,Config) of + {ok,ResAst} -> + ResAst; + {error,Errors} -> + ?DBG("Errors: ~p",[lists:flatten(Errors)]), + [error_to_ast(Line,Reason) || {Line,Reason} <- lists:flatten(Errors)] + end, + % ?DBG("~p~n<<<<~n",[Res]), + ?DBG("~s~n>>>>~n",[pretty_print(Res)]), + Res. +pretty_print(Ast) -> lists:flatten([erl_pp:form(N) || N<-Ast]). + +transform_ast(Ast,Config) -> + do([error_m || + ResAst <- dip_utils:error_writer_map(transform_node(_,Config),Ast), + return(lists:flatten([ Node || Node <- ResAst, Node =/= nil])) + ]). + + +%% =================================================================== +%%% Internal logic +%% =================================================================== + +transform_node({function,Line,Name,Arity,Clauses},Config) -> + do([error_m || + Clauses2 <- dip_utils:error_writer_map(parse_function_clause(_,Config),Clauses), + return({function,Line,Name,Arity,Clauses2})]); +transform_node(Node,_Config) -> + {ok,Node}. + +parse_function_clause({clause,Line,Arguments,Guards,Body},Config) -> + do([error_m || + Body2 <- parse(Body,Config), + return({clause,Line,Arguments,Guards,Body2})]). + +parse({call,_Line,{atom,_Line2,dip_orm},Args},Config) -> + parse_orm(Args,Config); +parse({call,Line,Remote,Args},Config) -> + do([error_m || + RArgs <- parse(Args,Config), + return({call,Line,Remote,RArgs})]); +parse(Nodes,Config) when is_list(Nodes) -> + dip_utils:error_writer_map(parse(_,Config),Nodes); +parse(Node,Config) when is_tuple(Node) andalso size(Node) > 2 -> + [Name,Line|Args] = tuple_to_list(Node), + do([error_m || + RArgs <- dip_utils:error_writer_map(parse(_,Config),Args), + return(list_to_tuple([Name,Line|RArgs])) + ]); +parse(Node,_Config) -> + {ok,Node}. + +parse_orm([{call,_Line,{atom,Line2,select},Models}|RestArgs],Configs) -> + Req = #request{type=select,configs=Configs}, + do([error_m || + case Models of + [] -> {error,{Line2,"Select request can't be empty"}}; + _ when length(Models) > 1 -> + {error,{Line2,"Only one select model allowed now"}}; + _ ->ok + end, + Req2 <- set_model(Models,Req), + {_RestArgs2,Req3} <- set_where(RestArgs,Req2), + Ast <- wel(Line2,request_to_sql(Req3)), + return(Ast) + ]); +parse_orm(_AST,_Configs) -> + {ok,{atom,0,not_yet_ready}}. + % {error,{123,"transform_error"}}. + + +%% =================================================================== +%%% Validators +%% =================================================================== + + +%% =================================================================== +%%% Internal functions +%% =================================================================== + +set_model([{atom,Line,ModelName}],#request{configs=Configs} = Req) -> + do([error_m || + Model <- wel(Line, + dip_orm_configs:find_model(ModelName,Configs)), + return(Req#request{ + model_to_select=Model + }) + ]); +set_model(Ast,_Acc) -> + ast_error(Ast,"Here must be valid atom"). + +%% =================================================================== + +set_where([{call,_Line,{atom,_Line2,where},WhereArgs}|RestArgs],Req) -> + do([error_m || + {Where,Req2} <- transform(WhereArgs,Req), + return({RestArgs, + Req2#request{ + where = Where + }}) + ]); +set_where([_Ast|RestArgs],Req) -> + {ok,{RestArgs,Req}}; +set_where([],Req) -> + {ok,{[],Req}}. + +%% =================================================================== + +transform([Ast],Req) -> + transform(Ast,Req); + +transform({op,_Line,Op,Arg1,Arg2},Req) when Op =:= 'andalso' orelse + Op =:= 'orelse' -> + do([error_m || + {[RArg1,RArg2],Req2} <- dip_utils:state_error_writer(Req, + [transform(Arg1,_), + transform(Arg2,_)]), + return({{Op,RArg1,RArg2},Req2}) + ]); + +transform({op,Line,Op,Field,Value},Req) when Op =:= '=:=' orelse + Op =:= '==' + -> + transform_op('=',Line,Field,Value,Req); +transform({op,Line,Op,Field,Value},Req) when Op =:= '=/=' orelse + Op =:= '/=' + -> + transform_op('=',Line,Field,{'not',Value},Req); +transform({op,Line,Op,Field,Value},Req) when Op =:= '<' orelse + Op =:= '>' -> + transform_op(Op,Line,Field,Value,Req); +transform({match,Line,_Field,_Value},_Req) -> + Reason = "Invlid compare operation. Must be '==' or '=:='.", + {error,{Line,Reason}}; +transform([_,Ast|_],_Req) -> + ast_error(Ast,"Arguments must be joined by 'andalso' or 'orelse' but not by ','"); +transform(Ast,_Req) -> + ast_error(Ast,"Wrong ORM Query operation"). + +transform_field({atom,Line,FieldName},Req) -> + do([error_m || + {ModelName,Field} <- wel(Line, + get_model_for_field(FieldName,Req)), + return({field,ModelName,Field})]); +transform_field({tuple,_Line,[{atom,Line2,ModelNameAtom},{atom,Line3,FieldName}]}, + #request{configs=Configs}) -> + ModelName = atom_to_binary(ModelNameAtom), + do([error_m || + Model <- wel(Line2, + dip_orm_configs:find_model(ModelName,Configs)), + Field <- wel(Line3, + dip_orm_configs:get_model_field(FieldName,Model)), + return({field,ModelName,Field}) + ]); +transform_field(Ast,_Req) -> + ast_error(Ast,"Syntax error field description"). + +transform_op(Op,Line,Field,Value,Req) -> + do([error_m || + Field2 <- transform_field(Field,Req), + Value2 <- transform_value(Value), + Req2 <- wel(Line, + set_link(Field2,Req)), + return({{Op,Field2,Value2},Req2})]). + +transform_value(Value) -> + {ok,Value}. + + +get_model_for_field(FieldName,#request{model_to_select=Model}) -> + case dip_orm_configs:get_model_field(FieldName,Model) of + {ok,Field} -> + SelectModelName = dip_orm_configs:model(name,Model), + {ok,{SelectModelName,Field}}; + {error,_Reason} -> + Reason = dip_utils:template("Unknown field name: ~p",[FieldName]), + {error,Reason} + end. + + +set_link({field,ModelName,_Field},#request{model_to_select=Model, + links=Links, + configs=Configs + } = Req) -> + SelectModelName = dip_orm_configs:model(name,Model), + case SelectModelName =:= ModelName of + true -> + {ok,Req}; + false -> + do([error_m || + RemoteModel <- dip_orm_configs:find_model(ModelName,Configs), + Link <- dip_orm_configs:find_link(Model,RemoteModel), + return(Req#request{links=[Link|Links]}) + ]) + end. + +%% =================================================================== + +request_to_sql(#request{type=select, + model_to_select = Model, + where = Where, + links = _Links}) -> + Fields = dip_orm_configs:model(db_fields,Model), + ModelName = dip_orm_configs:model(name,Model), + FieldsStr = [["\"",ModelName,"\".\"",dip_orm_configs:field(name,F),"\""] || F <- Fields], + FieldsStr2 = string:join(FieldsStr,","), + SQLHeader = ["SELECT ",FieldsStr2], + {WhereSQL,Args} = fold_where(Where), + + SQL2 = [SQLHeader," WHERE ",WhereSQL], + + Rest = {call,0, + {remote,0, + ast(atom,dip_db), + ast(atom,q)}, + [ + ast(string,dip_utils:template("~s",[SQL2])), + ast(list,lists:flatten(Args)) + ]}, + % {error,erl_prettypr:format(Rest)}; + % {ok,{atom,0,test}}; + {ok,Rest}; + + +request_to_sql(_) -> + {ok,{atom,0,not_yet_ready}}. + % {ok,{nil,0}}. + % {error,"Uncomplited"}. + +fold_where(undefined) -> + {[],[]}; +fold_where({'andalso',Left,Right}) -> + {SQL1,Arg1} = fold_where(Left), + {SQL2,Arg2} = fold_where(Right), + {["(",SQL1," AND ",SQL2,")"],[Arg1,Arg2]}; +fold_where({'orelse',Left,Right}) -> + {SQL1,Arg1} = fold_where(Left), + {SQL2,Arg2} = fold_where(Right), + {["(",SQL1," OR ",SQL2,")"],[Arg1,Arg2]}; +fold_where({'=',Field,Value}) -> + operation_to_sql("=",Field,Value); +fold_where({'>',Field,Value}) -> + operation_to_sql(">",Field,Value); +fold_where({'<',Field,Value}) -> + operation_to_sql("<",Field,Value). + +operation_to_sql(Op,{field,ModelName,Field},Value) -> + FieldName = dip_orm_configs:field(name,Field), + FieldDBType = dip_orm_configs:field(db_type,Field), + SQL = ["(\"",ModelName,"\".\"",FieldName,"\" ",Op," ~s)"], + case Value of + {'not',Value2} -> + {["NOT ",SQL],{tuple,0,[{atom,0,FieldDBType},Value2]}}; + _ -> + {SQL,{tuple,0,[{atom,0,FieldDBType},Value]}} + end. +%% =================================================================== + +% Wrap error line +wel(_Line,{error,{_Line2,_Reason}} = Err) -> Err; +wel(Line,{error,Reason}) -> {error,{Line,Reason}}; +wel(_Line,Result) -> Result. + +error_to_ast(Line, Reason)-> + {error,{Line,erl_parse,["orm error: ", io_lib:format("~s",[Reason]) ]}}. + +ast(F,Val) -> + erl_syntax:revert(erl_syntax:F(Val)). + +atom_to_binary(Atom) -> + list_to_binary( + atom_to_list(Atom)). + +ast_error(Ast,Reason) -> + Line = element(2,Ast), + {error,{Line,Reason}}. diff --git a/src/dip_utils.erl b/src/dip_utils.erl index 80d71c4..87889ce 100644 --- a/src/dip_utils.erl +++ b/src/dip_utils.erl @@ -8,12 +8,18 @@ %%%------------------------------------------------------------------- -module(dip_utils). +-include("log.hrl"). + -export([ success_fold/3, success_map/2, error_writer_fold/3, error_writer_map/2, + error_writer/1, + + state_error_writer/2, + map_filter/2, contains/2 ]). @@ -79,6 +85,35 @@ error_writer_map(Fun,List) when is_list(List) -> end, error_writer_fold(MapFun,[],List). +error_writer(List) -> + error_writer(List,[]). +error_writer([],[]) -> ok; +error_writer([],Errors) -> {error,Errors}; +error_writer([{error,Reason}|Rest],Errors) -> + error_writer(Rest,[Reason|Errors]); +error_writer([_|Rest],Errors) -> + error_writer(Rest,Errors). + +-spec state_error_writer(State,Funs) -> {ok,{Results,State}} | {error,Errors} when + Funs :: fun((State) -> {ok,Result} | {error,Reason}), + Results :: [Result], + Errors :: [Reason]. +state_error_writer(State,Funs) -> + state_error_writer(State,Funs,[],[]). +state_error_writer(State,[],Acc,[]) -> + {ok,{Acc,State}}; +state_error_writer(_State,[],_Acc,Errors) -> + {error,Errors}; +state_error_writer(State,[F|Rest],Acc,Errors) -> + case F(State) of + {ok,{Result,State2}} -> + state_error_writer(State2,Rest,[Result|Acc],Errors); + {error,Reason} -> + state_error_writer(State,Rest,Acc,[Reason|Errors]) + end. + + + map_filter(Fun,List) when is_list(List) -> map_filter(Fun,List,[]). map_filter(_Fun,[],Acc) -> @@ -103,7 +138,8 @@ contains(Option,[H|Rest]) -> %% =================================================================== template(Template,Args) -> - IOList = io_lib:format(Template,Args), + Template2 = lists:flatten(Template), + IOList = io_lib:format(Template2,Args), lists:flatten(IOList). %% =================================================================== diff --git a/src/file_bulders/dip_orm_config_file.erl b/src/file_bulders/dip_orm_config_file.erl index 01a4722..116b48b 100644 --- a/src/file_bulders/dip_orm_config_file.erl +++ b/src/file_bulders/dip_orm_config_file.erl @@ -21,8 +21,12 @@ write(ModuleName,Config, #global_config{output_src_folder=OutFolder}) -> Content = [ dip_orm_ast:module(ModuleName), - dip_orm_ast:export([{config,0}]), + dip_orm_ast:export([ + {config,0}, + {parse_transform,2} + ]), dip_orm_ast:spacer("API"), + parse_transform_function(), config_function(Config) ], ResultContent = dip_orm_ast:pretty_print(Content), @@ -35,7 +39,7 @@ write(ModuleName,Config, %% =================================================================== config_function(Config) -> - FunctionName = "config", + FunctionName = config, ConfigAst = erl_syntax:abstract(Config), FunctionAST = erl_syntax:function(erl_syntax:atom(FunctionName), [erl_syntax:clause( @@ -43,4 +47,25 @@ config_function(Config) -> [ConfigAst])]), erl_syntax:revert(FunctionAST). - +parse_transform_function() -> + FunctionName = parse_transform, + FunctionBody = erl_syntax:application( + erl_syntax:atom(dip_orm_parse_transform), + erl_syntax:atom(parse_transform), + [ + erl_syntax:variable("AST"), + erl_syntax:variable("Config"), + erl_syntax:application( + none, + erl_syntax:atom(config), + []) + ] + ), + FunctionAST = erl_syntax:function(erl_syntax:atom(FunctionName), + [erl_syntax:clause( + [ + erl_syntax:variable("AST"), + erl_syntax:variable("Config") + ], none, + [FunctionBody])]), + erl_syntax:revert(FunctionAST). diff --git a/src/file_bulders/dip_orm_model_file.erl b/src/file_bulders/dip_orm_model_file.erl index dc46fdb..7cb7140 100644 --- a/src/file_bulders/dip_orm_model_file.erl +++ b/src/file_bulders/dip_orm_model_file.erl @@ -26,6 +26,7 @@ write(#config{name=Name} = Config, #global_config{output_src_folder=OutFolder}) auto(start), dip_orm_ast:attribute(compile,[{parse_transform,do}]), dip_orm_ast:attribute(compile,[{parse_transform,cut}]), + dip_orm_ast:attribute(compile,[{parse_transform,dip_orm}]), dip_orm_ast:attribute(include,["log.hrl"]), dip_orm_ast:spacer("TYPES"), @@ -174,7 +175,8 @@ get_getters_and_setters_field(_) -> filtered. render_crud(#config{name=Name,fields=Fields}) -> ModuleName = atom_to_list(module_atom(Name)), IndexFields = get_index_fields(Fields), - {ok,Content} = crud_dtl:render([{model_name,ModuleName}, + {ok,Content} = crud_dtl:render([{model,binary_to_atom(Name)}, + {model_name,ModuleName}, {index_fields,IndexFields} ]), dip_orm_ast:raw(Content). @@ -300,9 +302,8 @@ binary_to_atom(Bin) -> list_to_atom(binary_to_list(Bin)). module_atom(Str) -> - list_to_atom( - binary_to_list( - <>)). + binary_to_atom( + <>). auto(start) -> Content =