Current section
Files
Jump to
Current section
Files
src/sqlode@internal@schema_parser.erl
-module(sqlode@internal@schema_parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/sqlode/internal/schema_parser.gleam").
-export([parse_files_with_engine/2, parse_files/1, error_to_string/1, warning_to_string/1]).
-export_type([parse_error/0, schema_warning/0, parsed_schema/0, view_column/0, statement_result/0, unknown_statement_kind/0, d_d_l_action/0, inline_enum_detection/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(false).
-type parse_error() :: {invalid_create_table, binary(), binary()} |
{invalid_column, binary(), binary(), binary()} |
{unsupported_mysql_ddl, binary(), binary()}.
-type schema_warning() :: {unresolvable_view_column, binary()}.
-type parsed_schema() :: {parsed_schema,
list(sqlode@internal@model:table()),
list(sqlode@internal@model:enum_def()),
list(schema_warning())}.
-type view_column() :: {view_column,
binary(),
list(sqlode@internal@lexer:token())}.
-type statement_result() :: {create_table_result,
sqlode@internal@model:table(),
list(sqlode@internal@model:enum_def())} |
{d_d_l_applied, d_d_l_action()} |
ignored.
-type unknown_statement_kind() :: {destructive_d_d_l, d_d_l_action()} |
silently_ignored |
{unsupported_mysql, binary()}.
-type d_d_l_action() :: {drop_table, binary()} |
{drop_view, binary()} |
{drop_type, binary()} |
{alter_drop_column, binary(), binary()} |
{alter_rename_table, binary(), binary()} |
{alter_rename_column, binary(), binary(), binary()} |
{alter_column_type, binary(), binary(), list(sqlode@internal@lexer:token())} |
{alter_modify_column,
binary(),
binary(),
list(sqlode@internal@lexer:token()),
boolean()} |
{alter_change_column,
binary(),
binary(),
binary(),
list(sqlode@internal@lexer:token()),
boolean()} |
{alter_column_set_not_null, binary(), binary()} |
{alter_column_drop_not_null, binary(), binary()}.
-type inline_enum_detection() :: {inline_enum,
sqlode@internal@model:scalar_type(),
sqlode@internal@model:enum_def()}.
-file("src/sqlode/internal/schema_parser.gleam", 175).
?DOC(false).
-spec split_token_statements(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@lexer:token()),
list(list(sqlode@internal@lexer:token()))
) -> list(list(sqlode@internal@lexer:token())).
split_token_statements(Tokens, Current, Acc) ->
case Tokens of
[] ->
case Current of
[] ->
lists:reverse(Acc);
_ ->
lists:reverse([lists:reverse(Current) | Acc])
end;
[semicolon | Rest] ->
case Current of
[] ->
split_token_statements(Rest, [], Acc);
_ ->
split_token_statements(
Rest,
[],
[lists:reverse(Current) | Acc]
)
end;
[Token | Rest@1] ->
split_token_statements(Rest@1, [Token | Current], Acc)
end.
-file("src/sqlode/internal/schema_parser.gleam", 202).
?DOC(false).
-spec is_create_enum_tokens(list(sqlode@internal@lexer:token())) -> boolean().
is_create_enum_tokens(Tokens) ->
case Tokens of
[{keyword, <<"create"/utf8>>},
{ident, T},
_,
{keyword, <<"as"/utf8>>},
{keyword, <<"enum"/utf8>>} |
_] when ((T =:= <<"type"/utf8>>) orelse (T =:= <<"TYPE"/utf8>>)) orelse (T =:= <<"Type"/utf8>>) ->
true;
_ ->
false
end.
-file("src/sqlode/internal/schema_parser.gleam", 246).
?DOC(false).
-spec extract_enum_values(list(sqlode@internal@lexer:token()), list(binary())) -> list(binary()).
extract_enum_values(Tokens, Acc) ->
case Tokens of
[] ->
lists:reverse(Acc);
[{string_lit, Value} | Rest] ->
extract_enum_values(Rest, [Value | Acc]);
[_ | Rest@1] ->
extract_enum_values(Rest@1, Acc)
end.
-file("src/sqlode/internal/schema_parser.gleam", 219).
?DOC(false).
-spec parse_create_enum_from_tokens(list(sqlode@internal@lexer:token())) -> {ok,
sqlode@internal@model:enum_def()} |
{error, nil}.
parse_create_enum_from_tokens(Tokens) ->
case Tokens of
[{keyword, <<"create"/utf8>>},
{ident, T},
Name_token,
{keyword, <<"as"/utf8>>},
{keyword, <<"enum"/utf8>>} |
Rest] when ((T =:= <<"type"/utf8>>) orelse (T =:= <<"TYPE"/utf8>>)) orelse (T =:= <<"Type"/utf8>>) ->
Name = case Name_token of
{ident, N} ->
string:lowercase(N);
{quoted_ident, N@1} ->
string:lowercase(N@1);
_ ->
<<""/utf8>>
end,
Values = extract_enum_values(Rest, []),
{ok, {enum_def, Name, Values, postgres_enum}};
_ ->
{error, nil}
end.
-file("src/sqlode/internal/schema_parser.gleam", 258).
?DOC(false).
-spec is_create_view_tokens(list(sqlode@internal@lexer:token())) -> boolean().
is_create_view_tokens(Tokens) ->
case Tokens of
[{keyword, <<"create"/utf8>>},
{keyword, <<"or"/utf8>>},
{keyword, <<"replace"/utf8>>},
{keyword, <<"view"/utf8>>} |
_] ->
true;
[{keyword, <<"create"/utf8>>}, {keyword, <<"view"/utf8>>} | _] ->
true;
_ ->
false
end.
-file("src/sqlode/internal/schema_parser.gleam", 313).
?DOC(false).
-spec extract_view_name_result(list(sqlode@internal@lexer:token())) -> gleam@option:option({binary(),
list(sqlode@internal@lexer:token())}).
extract_view_name_result(Tokens) ->
Remaining = case Tokens of
[{keyword, <<"create"/utf8>>},
{keyword, <<"or"/utf8>>},
{keyword, <<"replace"/utf8>>},
{keyword, <<"view"/utf8>>} |
Rest] ->
Rest;
[{keyword, <<"create"/utf8>>}, {keyword, <<"view"/utf8>>} | Rest@1] ->
Rest@1;
_ ->
[]
end,
case Remaining of
[{ident, N} | Rest@2] ->
{some, {string:lowercase(N), Rest@2}};
[{quoted_ident, N@1} | Rest@3] ->
{some, {string:lowercase(N@1), Rest@3}};
_ ->
none
end.
-file("src/sqlode/internal/schema_parser.gleam", 334).
?DOC(false).
-spec extract_after_select_result(list(sqlode@internal@lexer:token())) -> gleam@option:option(list(sqlode@internal@lexer:token())).
extract_after_select_result(Tokens) ->
case Tokens of
[{keyword, <<"select"/utf8>>} | Rest] ->
case Rest of
[] ->
none;
_ ->
{some, Rest}
end;
_ ->
none
end.
-file("src/sqlode/internal/schema_parser.gleam", 382).
?DOC(false).
-spec skip_to_keyword(list(sqlode@internal@lexer:token()), binary()) -> list(sqlode@internal@lexer:token()).
skip_to_keyword(Tokens, Keyword) ->
case Tokens of
[] ->
[];
[{keyword, K} | Rest] when K =:= Keyword ->
Rest;
[_ | Rest@1] ->
skip_to_keyword(Rest@1, Keyword)
end.
-file("src/sqlode/internal/schema_parser.gleam", 393).
?DOC(false).
-spec split_at_from(
list(sqlode@internal@lexer:token()),
integer(),
list(sqlode@internal@lexer:token())
) -> {list(sqlode@internal@lexer:token()), list(sqlode@internal@lexer:token())}.
split_at_from(Tokens, Depth, Acc) ->
case Tokens of
[] ->
{lists:reverse(Acc), []};
[l_paren | Rest] ->
split_at_from(Rest, Depth + 1, [l_paren | Acc]);
[r_paren | Rest@1] ->
split_at_from(Rest@1, Depth - 1, [r_paren | Acc]);
[{keyword, <<"from"/utf8>>} | Rest@2] when Depth =:= 0 ->
{lists:reverse(Acc), Rest@2};
[Token | Rest@3] ->
split_at_from(Rest@3, Depth, [Token | Acc])
end.
-file("src/sqlode/internal/schema_parser.gleam", 528).
?DOC(false).
-spec infer_cast_type(list(sqlode@internal@lexer:token())) -> gleam@option:option({sqlode@internal@model:scalar_type(),
boolean()}).
infer_cast_type(Tokens) ->
case Tokens of
[] ->
none;
[{keyword, <<"as"/utf8>>}, {ident, Type_name} | _] ->
case sqlode@internal@model:parse_sql_type(Type_name) of
{ok, Scalar_type} ->
{some, {Scalar_type, true}};
{error, _} ->
none
end;
[{keyword, <<"as"/utf8>>}, {keyword, Type_name} | _] ->
case sqlode@internal@model:parse_sql_type(Type_name) of
{ok, Scalar_type} ->
{some, {Scalar_type, true}};
{error, _} ->
none
end;
[_ | Rest] ->
infer_cast_type(Rest)
end.
-file("src/sqlode/internal/schema_parser.gleam", 543).
?DOC(false).
-spec tok_split_select_columns(
list(sqlode@internal@lexer:token()),
integer(),
list(sqlode@internal@lexer:token()),
list(list(sqlode@internal@lexer:token()))
) -> list(list(sqlode@internal@lexer:token())).
tok_split_select_columns(Tokens, Depth, Current, Acc) ->
case Tokens of
[] ->
case Current of
[] ->
lists:reverse(Acc);
_ ->
lists:reverse([lists:reverse(Current) | Acc])
end;
[comma | Rest] when Depth =:= 0 ->
tok_split_select_columns(
Rest,
0,
[],
[lists:reverse(Current) | Acc]
);
[l_paren | Rest@1] ->
tok_split_select_columns(
Rest@1,
Depth + 1,
[l_paren | Current],
Acc
);
[r_paren | Rest@2] ->
tok_split_select_columns(
Rest@2,
Depth - 1,
[r_paren | Current],
Acc
);
[Token | Rest@3] ->
tok_split_select_columns(Rest@3, Depth, [Token | Current], Acc)
end.
-file("src/sqlode/internal/schema_parser.gleam", 409).
?DOC(false).
-spec extract_view_columns(list(sqlode@internal@lexer:token())) -> list(view_column()).
extract_view_columns(Tokens) ->
Groups = tok_split_select_columns(Tokens, 0, [], []),
gleam@list:filter_map(
Groups,
fun(Col_tokens) ->
Reversed = lists:reverse(Col_tokens),
case Reversed of
[{ident, Alias}, {keyword, <<"as"/utf8>>} | Rest] ->
{ok, {view_column, Alias, lists:reverse(Rest)}};
[{quoted_ident, Alias@1}, {keyword, <<"as"/utf8>>} | Rest@1] ->
{ok, {view_column, Alias@1, lists:reverse(Rest@1)}};
_ ->
case lists:reverse(Reversed) of
[{ident, _}, dot, {ident, Col}] ->
{ok, {view_column, Col, Col_tokens}};
_ ->
case Reversed of
[{ident, Name} | _] ->
{ok, {view_column, Name, Col_tokens}};
[{quoted_ident, Name@1} | _] ->
{ok, {view_column, Name@1, Col_tokens}};
_ ->
{error, nil}
end
end
end
end
).
-file("src/sqlode/internal/schema_parser.gleam", 567).
?DOC(false).
-spec is_alter_table_add_column_tokens(list(sqlode@internal@lexer:token())) -> boolean().
is_alter_table_add_column_tokens(Tokens) ->
case Tokens of
[{keyword, <<"alter"/utf8>>},
{keyword, <<"table"/utf8>>},
_,
{keyword, <<"add"/utf8>>},
{keyword, <<"column"/utf8>>} |
_] ->
true;
[{keyword, <<"alter"/utf8>>},
{keyword, <<"table"/utf8>>},
_,
{keyword, <<"add"/utf8>>} |
Rest] ->
case Rest of
[{keyword, K} | _] when (((((K =:= <<"constraint"/utf8>>) orelse (K =:= <<"primary"/utf8>>)) orelse (K =:= <<"unique"/utf8>>)) orelse (K =:= <<"foreign"/utf8>>)) orelse (K =:= <<"check"/utf8>>)) orelse (K =:= <<"index"/utf8>>) ->
false;
_ ->
true
end;
_ ->
false
end.
-file("src/sqlode/internal/schema_parser.gleam", 669).
?DOC(false).
-spec strip_if_not_exists(list(sqlode@internal@lexer:token())) -> list(sqlode@internal@lexer:token()).
strip_if_not_exists(Tokens) ->
case Tokens of
[{keyword, <<"if"/utf8>>},
{keyword, <<"not"/utf8>>},
{keyword, <<"exists"/utf8>>} |
Rest] ->
Rest;
_ ->
Tokens
end.
-file("src/sqlode/internal/schema_parser.gleam", 677).
?DOC(false).
-spec extract_ident(sqlode@internal@lexer:token()) -> binary().
extract_ident(Token) ->
case Token of
{ident, N} ->
sqlode@internal@naming:normalize_identifier(N);
{quoted_ident, N@1} ->
sqlode@internal@naming:normalize_identifier(N@1);
_ ->
<<""/utf8>>
end.
-file("src/sqlode/internal/schema_parser.gleam", 640).
?DOC(false).
-spec extract_alter_table_parts(list(sqlode@internal@lexer:token())) -> {binary(),
list(sqlode@internal@lexer:token())}.
extract_alter_table_parts(Tokens) ->
case Tokens of
[{keyword, <<"alter"/utf8>>},
{keyword, <<"table"/utf8>>},
Name_tok,
{keyword, <<"add"/utf8>>},
{keyword, <<"column"/utf8>>} |
Rest] ->
{extract_ident(Name_tok), strip_if_not_exists(Rest)};
[{keyword, <<"alter"/utf8>>},
{keyword, <<"table"/utf8>>},
Name_tok@1,
{keyword, <<"add"/utf8>>} |
Rest@1] ->
{extract_ident(Name_tok@1), strip_if_not_exists(Rest@1)};
_ ->
{<<""/utf8>>, []}
end.
-file("src/sqlode/internal/schema_parser.gleam", 812).
?DOC(false).
-spec parse_drop_table(list(sqlode@internal@lexer:token())) -> unknown_statement_kind().
parse_drop_table(Tokens) ->
case Tokens of
[_,
_,
{keyword, <<"if"/utf8>>},
{keyword, <<"exists"/utf8>>},
Name_tok |
_] ->
{destructive_d_d_l, {drop_table, extract_ident(Name_tok)}};
[_, _, Name_tok@1 | _] ->
{destructive_d_d_l, {drop_table, extract_ident(Name_tok@1)}};
_ ->
silently_ignored
end.
-file("src/sqlode/internal/schema_parser.gleam", 823).
?DOC(false).
-spec parse_drop_view(list(sqlode@internal@lexer:token())) -> unknown_statement_kind().
parse_drop_view(Tokens) ->
case Tokens of
[_,
_,
{keyword, <<"if"/utf8>>},
{keyword, <<"exists"/utf8>>},
Name_tok |
_] ->
{destructive_d_d_l, {drop_view, extract_ident(Name_tok)}};
[_, _, Name_tok@1 | _] ->
{destructive_d_d_l, {drop_view, extract_ident(Name_tok@1)}};
_ ->
silently_ignored
end.
-file("src/sqlode/internal/schema_parser.gleam", 834).
?DOC(false).
-spec parse_drop_type(list(sqlode@internal@lexer:token())) -> unknown_statement_kind().
parse_drop_type(Tokens) ->
case Tokens of
[_,
_,
{keyword, <<"if"/utf8>>},
{keyword, <<"exists"/utf8>>},
Name_tok |
_] ->
{destructive_d_d_l, {drop_type, extract_ident(Name_tok)}};
[_, _, Name_tok@1 | _] ->
{destructive_d_d_l, {drop_type, extract_ident(Name_tok@1)}};
_ ->
silently_ignored
end.
-file("src/sqlode/internal/schema_parser.gleam", 863).
?DOC(false).
-spec skip_alter_modifiers(list(sqlode@internal@lexer:token())) -> {list(sqlode@internal@lexer:token()),
boolean()}.
skip_alter_modifiers(Tokens) ->
case Tokens of
[{keyword, <<"if"/utf8>>}, {keyword, <<"exists"/utf8>>} | Rest] ->
skip_alter_modifiers(Rest);
[{keyword, <<"only"/utf8>>} | Rest@1] ->
skip_alter_modifiers(Rest@1);
_ ->
{Tokens, false}
end.
-file("src/sqlode/internal/schema_parser.gleam", 984).
?DOC(false).
-spec extract_alter_column_name(list(sqlode@internal@lexer:token())) -> binary().
extract_alter_column_name(Tokens) ->
case Tokens of
[_, {keyword, <<"column"/utf8>>}, Name_tok | _] ->
extract_ident(Name_tok);
[_, Name_tok@1 | _] ->
extract_ident(Name_tok@1);
_ ->
<<""/utf8>>
end.
-file("src/sqlode/internal/schema_parser.gleam", 1008).
?DOC(false).
-spec take_until_using(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@lexer:token())
) -> list(sqlode@internal@lexer:token()).
take_until_using(Tokens, Acc) ->
case Tokens of
[] ->
lists:reverse(Acc);
[{keyword, <<"using"/utf8>>} | _] ->
lists:reverse(Acc);
[Tok | Rest] ->
take_until_using(Rest, [Tok | Acc])
end.
-file("src/sqlode/internal/schema_parser.gleam", 997).
?DOC(false).
-spec extract_alter_type_tokens(list(sqlode@internal@lexer:token())) -> list(sqlode@internal@lexer:token()).
extract_alter_type_tokens(Tokens) ->
case Tokens of
[] ->
[];
[{ident, T} | Rest] when ((T =:= <<"type"/utf8>>) orelse (T =:= <<"TYPE"/utf8>>)) orelse (T =:= <<"Type"/utf8>>) ->
take_until_using(Rest, []);
[_ | Rest@1] ->
extract_alter_type_tokens(Rest@1)
end.
-file("src/sqlode/internal/schema_parser.gleam", 1209).
?DOC(false).
-spec summarise_unknown_mysql_statement(list(binary())) -> binary().
summarise_unknown_mysql_statement(Words) ->
Summary = begin
_pipe = Words,
_pipe@1 = gleam@list:filter(_pipe, fun(Word) -> Word /= <<""/utf8>> end),
_pipe@2 = gleam@list:take(_pipe@1, 3),
_pipe@3 = gleam@list:map(_pipe@2, fun string:uppercase/1),
gleam@string:join(_pipe@3, <<" "/utf8>>)
end,
case Summary of
<<""/utf8>> ->
<<"(empty statement)"/utf8>>;
_ ->
<<<<Summary/binary,
" ... — sqlode does not (yet) understand this statement; file"/utf8>>/binary,
" an issue or pre-process the schema to remove it."/utf8>>
end.
-file("src/sqlode/internal/schema_parser.gleam", 1225).
?DOC(false).
-spec token_keyword_text(sqlode@internal@lexer:token()) -> binary().
token_keyword_text(Token) ->
case Token of
{keyword, K} ->
K;
{ident, I} ->
string:lowercase(I);
{quoted_ident, I@1} ->
string:lowercase(I@1);
_ ->
<<""/utf8>>
end.
-file("src/sqlode/internal/schema_parser.gleam", 1238).
?DOC(false).
-spec rewrite_column(
list(sqlode@internal@model:table()),
binary(),
binary(),
fun((sqlode@internal@model:column()) -> sqlode@internal@model:column())
) -> list(sqlode@internal@model:table()).
rewrite_column(Tables, Table_name, Column_name, Update) ->
gleam@list:map(
Tables,
fun(T) ->
case string:lowercase(erlang:element(2, T)) =:= string:lowercase(
Table_name
) of
true ->
{table,
erlang:element(2, T),
gleam@list:map(
erlang:element(3, T),
fun(C) ->
case string:lowercase(erlang:element(2, C)) =:= string:lowercase(
Column_name
) of
true ->
Update(C);
false ->
C
end
end
)};
false ->
T
end
end
).
-file("src/sqlode/internal/schema_parser.gleam", 1296).
?DOC(false).
-spec is_create_table_tokens(list(sqlode@internal@lexer:token())) -> boolean().
is_create_table_tokens(Tokens) ->
case Tokens of
[{keyword, <<"create"/utf8>>}, {keyword, <<"table"/utf8>>} | _] ->
true;
[{keyword, <<"create"/utf8>>},
{keyword, <<"temporary"/utf8>>},
{keyword, <<"table"/utf8>>} |
_] ->
true;
[{keyword, <<"create"/utf8>>},
{keyword, <<"temp"/utf8>>},
{keyword, <<"table"/utf8>>} |
_] ->
true;
[{keyword, <<"create"/utf8>>},
{keyword, <<"unlogged"/utf8>>},
{keyword, <<"table"/utf8>>} |
_] ->
true;
_ ->
false
end.
-file("src/sqlode/internal/schema_parser.gleam", 1317).
?DOC(false).
-spec find_column_in_tables(list(sqlode@internal@model:table()), binary()) -> gleam@option:option(sqlode@internal@model:column()).
find_column_in_tables(Tables, Column_name) ->
_pipe@1 = gleam@list:find_map(
Tables,
fun(Table) ->
_pipe = gleam@list:find(
erlang:element(3, Table),
fun(Col) ->
string:lowercase(erlang:element(2, Col)) =:= string:lowercase(
Column_name
)
end
),
gleam@result:map_error(_pipe, fun(_) -> nil end)
end
),
gleam@option:from_result(_pipe@1).
-file("src/sqlode/internal/schema_parser.gleam", 439).
?DOC(false).
-spec resolve_column_from_expr_tokens(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:table())
) -> gleam@option:option(sqlode@internal@model:column()).
resolve_column_from_expr_tokens(Expr_tokens, Tables) ->
case Expr_tokens of
[{ident, Name}] ->
find_column_in_tables(Tables, string:lowercase(Name));
[{quoted_ident, Name@1}] ->
find_column_in_tables(Tables, string:lowercase(Name@1));
[{ident, _}, dot, {ident, Col}] ->
find_column_in_tables(Tables, string:lowercase(Col));
[{quoted_ident, _}, dot, {ident, Col@1}] ->
find_column_in_tables(Tables, string:lowercase(Col@1));
_ ->
none
end.
-file("src/sqlode/internal/schema_parser.gleam", 511).
?DOC(false).
-spec extract_aggregate_inner_type(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:table())
) -> gleam@option:option(sqlode@internal@model:column()).
extract_aggregate_inner_type(Tokens, Tables) ->
case Tokens of
[{ident, Name}, r_paren | _] ->
find_column_in_tables(Tables, string:lowercase(Name));
[{ident, Name}, comma | _] ->
find_column_in_tables(Tables, string:lowercase(Name));
[{ident, _}, dot, {ident, Col}, r_paren | _] ->
find_column_in_tables(Tables, string:lowercase(Col));
[{ident, _}, dot, {ident, Col}, comma | _] ->
find_column_in_tables(Tables, string:lowercase(Col));
[{keyword, <<"distinct"/utf8>>}, {ident, Name@1}, r_paren | _] ->
find_column_in_tables(Tables, string:lowercase(Name@1));
[{keyword, <<"distinct"/utf8>>}, {ident, Name@1}, comma | _] ->
find_column_in_tables(Tables, string:lowercase(Name@1));
_ ->
none
end.
-file("src/sqlode/internal/schema_parser.gleam", 455).
?DOC(false).
-spec infer_view_expression_type(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:table())
) -> gleam@option:option({sqlode@internal@model:scalar_type(), boolean()}).
infer_view_expression_type(Expr_tokens, Tables) ->
case Expr_tokens of
[{keyword, <<"cast"/utf8>>}, l_paren | Rest] ->
infer_cast_type(Rest);
[{ident, Fn_name}, l_paren | Rest@1] ->
Lowered = string:lowercase(Fn_name),
case Lowered of
<<"count"/utf8>> ->
{some, {int_type, false}};
<<"avg"/utf8>> ->
{some, {case extract_aggregate_inner_type(Rest@1, Tables) of
{some, Col} ->
case erlang:element(3, Col) of
int_type ->
float_type;
float_type ->
float_type;
Other ->
Other
end;
none ->
float_type
end, true}};
<<"sum"/utf8>> ->
case extract_aggregate_inner_type(Rest@1, Tables) of
{some, Col@1} ->
{some, {erlang:element(3, Col@1), true}};
none ->
{some, {float_type, true}}
end;
<<"min"/utf8>> ->
case extract_aggregate_inner_type(Rest@1, Tables) of
{some, Col@2} ->
{some, {erlang:element(3, Col@2), true}};
none ->
none
end;
<<"max"/utf8>> ->
case extract_aggregate_inner_type(Rest@1, Tables) of
{some, Col@2} ->
{some, {erlang:element(3, Col@2), true}};
none ->
none
end;
<<"coalesce"/utf8>> ->
case extract_aggregate_inner_type(Rest@1, Tables) of
{some, Col@3} ->
{some, {erlang:element(3, Col@3), false}};
none ->
none
end;
<<"row_number"/utf8>> ->
{some, {int_type, false}};
<<"rank"/utf8>> ->
{some, {int_type, false}};
<<"dense_rank"/utf8>> ->
{some, {int_type, false}};
_ ->
none
end;
[{string_lit, _} | _] ->
{some, {string_type, false}};
[{number_lit, N} | _] ->
case gleam_stdlib:contains_string(N, <<"."/utf8>>) of
true ->
{some, {float_type, false}};
false ->
{some, {int_type, false}}
end;
_ ->
none
end.
-file("src/sqlode/internal/schema_parser.gleam", 362).
?DOC(false).
-spec resolve_single_view_column(
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:table())
) -> {ok, sqlode@internal@model:column()} | {error, schema_warning()}.
resolve_single_view_column(Name, Expr_tokens, Tables) ->
case find_column_in_tables(Tables, Name) of
{some, Col} ->
{ok, {column, Name, erlang:element(3, Col), erlang:element(4, Col)}};
none ->
case resolve_column_from_expr_tokens(Expr_tokens, Tables) of
{some, Col@1} ->
{ok,
{column,
Name,
erlang:element(3, Col@1),
erlang:element(4, Col@1)}};
none ->
case infer_view_expression_type(Expr_tokens, Tables) of
{some, {Scalar_type, Nullable}} ->
{ok, {column, Name, Scalar_type, Nullable}};
none ->
{error, {unresolvable_view_column, Name}}
end
end
end.
-file("src/sqlode/internal/schema_parser.gleam", 347).
?DOC(false).
-spec resolve_view_columns(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:table())
) -> {list(sqlode@internal@model:column()), list(schema_warning())}.
resolve_view_columns(Select_tokens, Tables) ->
View_cols = extract_view_columns(Select_tokens),
gleam@list:fold(
View_cols,
{[], []},
fun(Acc, View_col) ->
{Columns, Warnings} = Acc,
Normalized = sqlode@internal@naming:normalize_identifier(
erlang:element(2, View_col)
),
case resolve_single_view_column(
Normalized,
erlang:element(3, View_col),
Tables
) of
{ok, Col} ->
{lists:append(Columns, [Col]), Warnings};
{error, Warning} ->
{Columns, lists:append(Warnings, [Warning])}
end
end
).
-file("src/sqlode/internal/schema_parser.gleam", 272).
?DOC(false).
-spec parse_create_view_from_tokens(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:table())
) -> {gleam@option:option(sqlode@internal@model:table()),
list(schema_warning())}.
parse_create_view_from_tokens(Tokens, Tables) ->
case extract_view_name_result(Tokens) of
none ->
{none, []};
{some, {View_name, After_name}} ->
After_as = skip_to_keyword(After_name, <<"as"/utf8>>),
case extract_after_select_result(After_as) of
none ->
{none, []};
{some, After_select} ->
{Select_tokens, From_tokens} = split_at_from(
After_select,
0,
[]
),
Source_tables = sqlode@internal@query_analyzer@token_utils:extract_table_names(
[{keyword, <<"from"/utf8>>} | From_tokens]
),
{Columns, Warnings} = case Select_tokens of
[star] ->
{gleam@list:flat_map(
Source_tables,
fun(Table_name) ->
case gleam@list:find(
Tables,
fun(T) ->
erlang:element(2, T) =:= Table_name
end
) of
{ok, Table} ->
erlang:element(3, Table);
{error, _} ->
[]
end
end
),
[]};
_ ->
resolve_view_columns(Select_tokens, Tables)
end,
case Columns of
[] ->
{none, Warnings};
_ ->
{{some, {table, View_name, Columns}}, Warnings}
end
end
end.
-file("src/sqlode/internal/schema_parser.gleam", 1369).
?DOC(false).
-spec split_at_lparen(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@lexer:token())
) -> {list(sqlode@internal@lexer:token()), list(sqlode@internal@lexer:token())}.
split_at_lparen(Tokens, Header) ->
case Tokens of
[] ->
{lists:reverse(Header), []};
[l_paren | Rest] ->
{lists:reverse(Header), Rest};
[Tok | Rest@1] ->
split_at_lparen(Rest@1, [Tok | Header])
end.
-file("src/sqlode/internal/schema_parser.gleam", 1381).
?DOC(false).
-spec find_last_ident(list(sqlode@internal@lexer:token())) -> {ok, binary()} |
{error, nil}.
find_last_ident(Tokens) ->
_pipe = Tokens,
_pipe@1 = gleam@list:filter_map(_pipe, fun(Tok) -> case Tok of
{ident, N} ->
{ok, sqlode@internal@naming:normalize_identifier(N)};
{quoted_ident, N} ->
{ok, sqlode@internal@naming:normalize_identifier(N)};
_ ->
{error, nil}
end end),
gleam@list:last(_pipe@1).
-file("src/sqlode/internal/schema_parser.gleam", 1401).
?DOC(false).
-spec drop_trailing_rparens(list(sqlode@internal@lexer:token())) -> list(sqlode@internal@lexer:token()).
drop_trailing_rparens(Rev_tokens) ->
case Rev_tokens of
[r_paren | Rest] ->
Rest;
_ ->
Rev_tokens
end.
-file("src/sqlode/internal/schema_parser.gleam", 1394).
?DOC(false).
-spec strip_trailing_rparen(list(sqlode@internal@lexer:token())) -> list(sqlode@internal@lexer:token()).
strip_trailing_rparen(Tokens) ->
_pipe = Tokens,
_pipe@1 = lists:reverse(_pipe),
_pipe@2 = drop_trailing_rparens(_pipe@1),
lists:reverse(_pipe@2).
-file("src/sqlode/internal/schema_parser.gleam", 1445).
?DOC(false).
-spec split_tokens_by_comma_loop(
list(sqlode@internal@lexer:token()),
integer(),
list(sqlode@internal@lexer:token()),
list(list(sqlode@internal@lexer:token()))
) -> list(list(sqlode@internal@lexer:token())).
split_tokens_by_comma_loop(Tokens, Depth, Current, Acc) ->
case Tokens of
[] ->
case Current of
[] ->
lists:reverse(Acc);
_ ->
lists:reverse([lists:reverse(Current) | Acc])
end;
[l_paren | Rest] ->
split_tokens_by_comma_loop(
Rest,
Depth + 1,
[l_paren | Current],
Acc
);
[r_paren | Rest@1] ->
split_tokens_by_comma_loop(Rest@1, case Depth > 0 of
true ->
Depth - 1;
false ->
0
end, [r_paren | Current], Acc);
[comma | Rest@2] when Depth =:= 0 ->
split_tokens_by_comma_loop(
Rest@2,
Depth,
[],
[lists:reverse(Current) | Acc]
);
[Tok | Rest@3] ->
split_tokens_by_comma_loop(Rest@3, Depth, [Tok | Current], Acc)
end.
-file("src/sqlode/internal/schema_parser.gleam", 1441).
?DOC(false).
-spec split_tokens_by_comma(list(sqlode@internal@lexer:token())) -> list(list(sqlode@internal@lexer:token())).
split_tokens_by_comma(Tokens) ->
split_tokens_by_comma_loop(Tokens, 0, [], []).
-file("src/sqlode/internal/schema_parser.gleam", 1655).
?DOC(false).
-spec synthetic_enum_name(binary(), binary()) -> binary().
synthetic_enum_name(Table_name, Column_name) ->
<<<<(string:lowercase(Table_name))/binary, "_"/utf8>>/binary,
(string:lowercase(Column_name))/binary>>.
-file("src/sqlode/internal/schema_parser.gleam", 1616).
?DOC(false).
-spec detect_mysql_inline_enum_set(
list(sqlode@internal@lexer:token()),
binary(),
binary(),
sqlode@internal@model:engine()
) -> gleam@option:option(inline_enum_detection()).
detect_mysql_inline_enum_set(Type_toks, Table_name, Column_name, Engine) ->
case Engine of
my_s_q_l ->
case Type_toks of
[{keyword, <<"enum"/utf8>>}, l_paren | Rest] ->
Values = extract_enum_values(Rest, []),
Synthetic = synthetic_enum_name(Table_name, Column_name),
{some,
{inline_enum,
{enum_type, Synthetic},
{enum_def, Synthetic, Values, my_sql_enum}}};
[{keyword, <<"set"/utf8>>}, l_paren | Rest@1] ->
Values@1 = extract_enum_values(Rest@1, []),
Synthetic@1 = synthetic_enum_name(Table_name, Column_name),
{some,
{inline_enum,
{set_type, Synthetic@1},
{enum_def, Synthetic@1, Values@1, my_sql_set}}};
_ ->
none
end;
_ ->
none
end.
-file("src/sqlode/internal/schema_parser.gleam", 1687).
?DOC(false).
-spec render_type_tokens_loop(
list(sqlode@internal@lexer:token()),
list(binary())
) -> list(binary()).
render_type_tokens_loop(Tokens, Acc) ->
case Tokens of
[] ->
Acc;
[{operator, <<"["/utf8>>}, {operator, <<"]"/utf8>>} | Rest] ->
case Acc of
[Prev | Prev_rest] ->
render_type_tokens_loop(
Rest,
[<<Prev/binary, "[]"/utf8>> | Prev_rest]
);
[] ->
render_type_tokens_loop(Rest, [<<"[]"/utf8>> | Acc])
end;
[Tok | Rest@1] ->
Token_text = case Tok of
{keyword, K} ->
K;
{ident, N} ->
N;
{quoted_ident, N@1} ->
N@1;
{number_lit, N@2} ->
N@2;
{operator, Op} ->
Op;
l_paren ->
<<"("/utf8>>;
r_paren ->
<<")"/utf8>>;
comma ->
<<","/utf8>>;
star ->
<<"*"/utf8>>;
_ ->
<<""/utf8>>
end,
case Token_text of
<<""/utf8>> ->
render_type_tokens_loop(Rest@1, Acc);
_ ->
render_type_tokens_loop(Rest@1, [Token_text | Acc])
end
end.
-file("src/sqlode/internal/schema_parser.gleam", 1681).
?DOC(false).
-spec render_type_tokens(list(sqlode@internal@lexer:token())) -> binary().
render_type_tokens(Tokens) ->
_pipe = render_type_tokens_loop(Tokens, []),
_pipe@1 = lists:reverse(_pipe),
gleam@string:join(_pipe@1, <<" "/utf8>>).
-file("src/sqlode/internal/schema_parser.gleam", 1721).
?DOC(false).
-spec tokens_contain_not_null(list(sqlode@internal@lexer:token())) -> boolean().
tokens_contain_not_null(Tokens) ->
case Tokens of
[] ->
false;
[_] ->
false;
[{keyword, <<"not"/utf8>>}, {keyword, <<"null"/utf8>>} | _] ->
true;
[_ | Rest] ->
tokens_contain_not_null(Rest)
end.
-file("src/sqlode/internal/schema_parser.gleam", 1729).
?DOC(false).
-spec tokens_contain_keyword(list(sqlode@internal@lexer:token()), binary()) -> boolean().
tokens_contain_keyword(Tokens, Keyword) ->
gleam@list:any(Tokens, fun(Tok) -> case Tok of
{keyword, K} ->
K =:= Keyword;
_ ->
false
end end).
-file("src/sqlode/internal/schema_parser.gleam", 1738).
?DOC(false).
-spec infer_scalar_type_for_engine(binary(), sqlode@internal@model:engine()) -> {ok,
sqlode@internal@model:scalar_type()} |
{error, binary()}.
infer_scalar_type_for_engine(Type_text, Engine) ->
_pipe = sqlode@internal@model:parse_sql_type_for_engine(Type_text, Engine),
gleam@result:replace_error(
_pipe,
<<<<<<<<<<"unrecognized SQL type \""/utf8, Type_text/binary>>/binary,
"\". Supported types: int, serial, float, numeric, decimal, bool, text,"/utf8>>/binary,
" char, bytea, uuid, json, jsonb, timestamp, datetime, date, time,"/utf8>>/binary,
" interval. Hint: add a type override in sqlode.yaml under"/utf8>>/binary,
" overrides.db_type"/utf8>>
).
-file("src/sqlode/internal/schema_parser.gleam", 1753).
?DOC(false).
-spec is_column_constraint(binary()) -> boolean().
is_column_constraint(Keyword) ->
gleam@list:contains(
[<<"not"/utf8>>,
<<"null"/utf8>>,
<<"primary"/utf8>>,
<<"unique"/utf8>>,
<<"default"/utf8>>,
<<"references"/utf8>>,
<<"check"/utf8>>,
<<"constraint"/utf8>>,
<<"generated"/utf8>>,
<<"collate"/utf8>>,
<<"autoincrement"/utf8>>,
<<"auto_increment"/utf8>>,
<<"on"/utf8>>,
<<"character"/utf8>>,
<<"comment"/utf8>>,
<<"invisible"/utf8>>,
<<"visible"/utf8>>],
Keyword
).
-file("src/sqlode/internal/schema_parser.gleam", 1659).
?DOC(false).
-spec take_type_tokens_from_lexer(
list(sqlode@internal@lexer:token()),
list(sqlode@internal@lexer:token())
) -> list(sqlode@internal@lexer:token()).
take_type_tokens_from_lexer(Tokens, Acc) ->
case Tokens of
[] ->
lists:reverse(Acc);
[{keyword, K} | Rest] ->
case is_column_constraint(K) of
true ->
lists:reverse(Acc);
false ->
take_type_tokens_from_lexer(Rest, [{keyword, K} | Acc])
end;
[{ident, Name} | Rest@1] ->
case is_column_constraint(string:lowercase(Name)) of
true ->
lists:reverse(Acc);
false ->
take_type_tokens_from_lexer(Rest@1, [{ident, Name} | Acc])
end;
[Tok | Rest@2] ->
take_type_tokens_from_lexer(Rest@2, [Tok | Acc])
end.
-file("src/sqlode/internal/schema_parser.gleam", 929).
?DOC(false).
-spec classify_mysql_modify(binary(), list(sqlode@internal@lexer:token())) -> unknown_statement_kind().
classify_mysql_modify(Table_name, Tokens) ->
After_modify = case Tokens of
[_, {keyword, <<"column"/utf8>>} | Rest] ->
Rest;
[_ | Rest@1] ->
Rest@1;
_ ->
[]
end,
case After_modify of
[Name_tok | Rest@2] ->
Column_name = extract_ident(Name_tok),
Type_tokens = take_type_tokens_from_lexer(Rest@2, []),
Nullable = not tokens_contain_not_null(Rest@2),
{destructive_d_d_l,
{alter_modify_column,
Table_name,
Column_name,
Type_tokens,
Nullable}};
[] ->
silently_ignored
end.
-file("src/sqlode/internal/schema_parser.gleam", 956).
?DOC(false).
-spec classify_mysql_change(binary(), list(sqlode@internal@lexer:token())) -> unknown_statement_kind().
classify_mysql_change(Table_name, Tokens) ->
After_change = case Tokens of
[_, {keyword, <<"column"/utf8>>} | Rest] ->
Rest;
[_ | Rest@1] ->
Rest@1;
_ ->
[]
end,
case After_change of
[Old_tok, New_tok | Rest@2] ->
Old_name = extract_ident(Old_tok),
New_name = extract_ident(New_tok),
Type_tokens = take_type_tokens_from_lexer(Rest@2, []),
Nullable = not tokens_contain_not_null(Rest@2),
{destructive_d_d_l,
{alter_change_column,
Table_name,
Old_name,
New_name,
Type_tokens,
Nullable}};
_ ->
silently_ignored
end.
-file("src/sqlode/internal/schema_parser.gleam", 872).
?DOC(false).
-spec classify_alter_action_tokens(
binary(),
list(sqlode@internal@lexer:token())
) -> unknown_statement_kind().
classify_alter_action_tokens(Table_name, Tokens) ->
Words = begin
_pipe = Tokens,
_pipe@1 = gleam@list:take(_pipe, 8),
gleam@list:map(_pipe@1, fun token_keyword_text/1)
end,
case Words of
[<<"drop"/utf8>>, <<"constraint"/utf8>> | _] ->
silently_ignored;
[<<"drop"/utf8>>,
<<"column"/utf8>>,
<<"if"/utf8>>,
<<"exists"/utf8>>,
Col_name |
_] ->
{destructive_d_d_l, {alter_drop_column, Table_name, Col_name}};
[<<"drop"/utf8>>, <<"column"/utf8>>, Col_name@1 | _] ->
{destructive_d_d_l, {alter_drop_column, Table_name, Col_name@1}};
[<<"drop"/utf8>>, Col_name@2 | _] ->
{destructive_d_d_l, {alter_drop_column, Table_name, Col_name@2}};
[<<"rename"/utf8>>, <<"to"/utf8>>, New_name | _] ->
{destructive_d_d_l, {alter_rename_table, Table_name, New_name}};
[<<"rename"/utf8>>,
<<"column"/utf8>>,
Old_name,
<<"to"/utf8>>,
New_name@1 |
_] ->
{destructive_d_d_l,
{alter_rename_column, Table_name, Old_name, New_name@1}};
[<<"rename"/utf8>>, Old_name@1, <<"to"/utf8>>, New_name@2 | _] ->
{destructive_d_d_l,
{alter_rename_column, Table_name, Old_name@1, New_name@2}};
[<<"alter"/utf8>>,
<<"column"/utf8>>,
_,
<<"set"/utf8>>,
<<"not"/utf8>>,
<<"null"/utf8>> |
_] ->
{destructive_d_d_l,
{alter_column_set_not_null,
Table_name,
extract_alter_column_name(Tokens)}};
[<<"alter"/utf8>>,
<<"column"/utf8>>,
_,
<<"drop"/utf8>>,
<<"not"/utf8>>,
<<"null"/utf8>> |
_] ->
{destructive_d_d_l,
{alter_column_drop_not_null,
Table_name,
extract_alter_column_name(Tokens)}};
[<<"alter"/utf8>>, <<"column"/utf8>>, _, <<"type"/utf8>> | _] ->
{destructive_d_d_l,
{alter_column_type,
Table_name,
extract_alter_column_name(Tokens),
extract_alter_type_tokens(Tokens)}};
[<<"alter"/utf8>>,
<<"column"/utf8>>,
_,
<<"set"/utf8>>,
<<"data"/utf8>>,
<<"type"/utf8>> |
_] ->
{destructive_d_d_l,
{alter_column_type,
Table_name,
extract_alter_column_name(Tokens),
extract_alter_type_tokens(Tokens)}};
[<<"modify"/utf8>>, <<"column"/utf8>> | _] ->
classify_mysql_modify(Table_name, Tokens);
[<<"modify"/utf8>>, _ | _] ->
classify_mysql_modify(Table_name, Tokens);
[<<"change"/utf8>>, <<"column"/utf8>> | _] ->
classify_mysql_change(Table_name, Tokens);
[<<"change"/utf8>>, _, _ | _] ->
classify_mysql_change(Table_name, Tokens);
[<<"add"/utf8>>, <<"constraint"/utf8>> | _] ->
silently_ignored;
[<<"add"/utf8>>, <<"primary"/utf8>> | _] ->
silently_ignored;
[<<"add"/utf8>>, <<"unique"/utf8>> | _] ->
silently_ignored;
[<<"add"/utf8>>, <<"foreign"/utf8>> | _] ->
silently_ignored;
[<<"add"/utf8>>, <<"check"/utf8>> | _] ->
silently_ignored;
[<<"add"/utf8>>, <<"index"/utf8>> | _] ->
silently_ignored;
_ ->
silently_ignored
end.
-file("src/sqlode/internal/schema_parser.gleam", 845).
?DOC(false).
-spec classify_alter_table_from_tokens(list(sqlode@internal@lexer:token())) -> unknown_statement_kind().
classify_alter_table_from_tokens(Tokens) ->
After_alter_table = case Tokens of
[_, _ | Rest] ->
Rest;
_ ->
[]
end,
{After_modifiers, _} = skip_alter_modifiers(After_alter_table),
case After_modifiers of
[Name_tok | Rest@1] ->
Table_name = extract_ident(Name_tok),
classify_alter_action_tokens(Table_name, Rest@1);
[] ->
silently_ignored
end.
-file("src/sqlode/internal/schema_parser.gleam", 773).
?DOC(false).
-spec classify_unknown_statement(
list(sqlode@internal@lexer:token()),
sqlode@internal@model:engine()
) -> unknown_statement_kind().
classify_unknown_statement(Tokens, Engine) ->
Words = begin
_pipe = Tokens,
_pipe@1 = gleam@list:take(_pipe, 8),
gleam@list:map(_pipe@1, fun token_keyword_text/1)
end,
case Words of
[<<"create"/utf8>>, <<"index"/utf8>> | _] ->
silently_ignored;
[<<"create"/utf8>>, <<"unique"/utf8>>, <<"index"/utf8>> | _] ->
silently_ignored;
[<<"drop"/utf8>>, <<"index"/utf8>> | _] ->
silently_ignored;
[<<"comment"/utf8>>, <<"on"/utf8>> | _] ->
silently_ignored;
[<<"begin"/utf8>> | _] ->
silently_ignored;
[<<"commit"/utf8>> | _] ->
silently_ignored;
[<<"rollback"/utf8>> | _] ->
silently_ignored;
[<<"savepoint"/utf8>> | _] ->
silently_ignored;
[<<"release"/utf8>> | _] ->
silently_ignored;
[<<"set"/utf8>> | _] ->
silently_ignored;
[<<"drop"/utf8>>, <<"table"/utf8>> | _] ->
parse_drop_table(Tokens);
[<<"drop"/utf8>>, <<"view"/utf8>> | _] ->
parse_drop_view(Tokens);
[<<"drop"/utf8>>, <<"type"/utf8>> | _] ->
parse_drop_type(Tokens);
[<<"alter"/utf8>>, <<"table"/utf8>> | _] ->
classify_alter_table_from_tokens(Tokens);
_ ->
case Engine of
my_s_q_l ->
{unsupported_mysql,
summarise_unknown_mysql_statement(Words)};
postgre_s_q_l ->
silently_ignored;
s_q_lite ->
silently_ignored
end
end.
-file("src/sqlode/internal/schema_parser.gleam", 1782).
?DOC(false).
-spec find_enum(binary(), list(sqlode@internal@model:enum_def())) -> gleam@option:option(binary()).
find_enum(Type_text, Enums) ->
Lowered = string:lowercase(gleam@string:trim(Type_text)),
case gleam@list:find(Enums, fun(E) -> erlang:element(2, E) =:= Lowered end) of
{ok, E@1} ->
{some, erlang:element(2, E@1)};
{error, _} ->
none
end.
-file("src/sqlode/internal/schema_parser.gleam", 1268).
?DOC(false).
-spec resolve_mysql_alter_type(
list(sqlode@internal@lexer:token()),
binary(),
binary(),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {sqlode@internal@model:scalar_type(),
list(sqlode@internal@model:enum_def())}.
resolve_mysql_alter_type(Type_tokens, Table_name, Column_name, Enums, Engine) ->
case detect_mysql_inline_enum_set(
Type_tokens,
Table_name,
Column_name,
Engine
) of
{some, {inline_enum, Scalar_type, New_enum}} ->
{Scalar_type, lists:append(Enums, [New_enum])};
none ->
Type_text = render_type_tokens(Type_tokens),
Resolved = case find_enum(Type_text, Enums) of
{some, Enum_name} ->
{ok, {enum_type, Enum_name}};
none ->
sqlode@internal@model:parse_sql_type_for_engine(
Type_text,
Engine
)
end,
case Resolved of
{ok, T} ->
{T, Enums};
{error, _} ->
{string_type, Enums}
end
end.
-file("src/sqlode/internal/schema_parser.gleam", 1024).
?DOC(false).
-spec apply_ddl_action(
d_d_l_action(),
binary(),
list(sqlode@internal@model:table()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok,
{list(sqlode@internal@model:table()),
list(sqlode@internal@model:enum_def())}} |
{error, parse_error()}.
apply_ddl_action(Action, Path, Tables, Enums, Engine) ->
case Action of
{drop_table, Table_name} ->
{ok,
{gleam@list:filter(
Tables,
fun(T) ->
string:lowercase(erlang:element(2, T)) /= string:lowercase(
Table_name
)
end
),
Enums}};
{drop_view, View_name} ->
{ok,
{gleam@list:filter(
Tables,
fun(T@1) ->
string:lowercase(erlang:element(2, T@1)) /= string:lowercase(
View_name
)
end
),
Enums}};
{drop_type, Type_name} ->
{ok,
{Tables,
gleam@list:filter(
Enums,
fun(E) ->
string:lowercase(erlang:element(2, E)) /= string:lowercase(
Type_name
)
end
)}};
{alter_drop_column, Table_name@1, Column_name} ->
{ok,
{gleam@list:map(
Tables,
fun(T@2) ->
case string:lowercase(erlang:element(2, T@2)) =:= string:lowercase(
Table_name@1
) of
true ->
{table,
erlang:element(2, T@2),
gleam@list:filter(
erlang:element(3, T@2),
fun(C) ->
string:lowercase(
erlang:element(2, C)
)
/= string:lowercase(Column_name)
end
)};
false ->
T@2
end
end
),
Enums}};
{alter_rename_table, Old_name, New_name} ->
{ok,
{gleam@list:map(
Tables,
fun(T@3) ->
case string:lowercase(erlang:element(2, T@3)) =:= string:lowercase(
Old_name
) of
true ->
{table,
string:lowercase(New_name),
erlang:element(3, T@3)};
false ->
T@3
end
end
),
Enums}};
{alter_rename_column, Table_name@2, Old_name@1, New_name@1} ->
{ok,
{gleam@list:map(
Tables,
fun(T@4) ->
case string:lowercase(erlang:element(2, T@4)) =:= string:lowercase(
Table_name@2
) of
true ->
{table,
erlang:element(2, T@4),
gleam@list:map(
erlang:element(3, T@4),
fun(C@1) ->
case string:lowercase(
erlang:element(2, C@1)
)
=:= string:lowercase(Old_name@1) of
true ->
{column,
string:lowercase(
New_name@1
),
erlang:element(
3,
C@1
),
erlang:element(
4,
C@1
)};
false ->
C@1
end
end
)};
false ->
T@4
end
end
),
Enums}};
{alter_column_type, Table_name@3, Column_name@1, Type_tokens} ->
Type_text = render_type_tokens(Type_tokens),
case sqlode@internal@model:parse_sql_type_for_engine(
Type_text,
Engine
) of
{ok, Scalar_type} ->
{ok,
{rewrite_column(
Tables,
Table_name@3,
Column_name@1,
fun(C@2) ->
{column,
erlang:element(2, C@2),
Scalar_type,
erlang:element(4, C@2)}
end
),
Enums}};
{error, _} ->
{error,
{invalid_column,
Path,
Table_name@3,
<<<<<<<<"ALTER COLUMN \""/utf8,
Column_name@1/binary>>/binary,
"\" TYPE: unsupported or unrecognized type \""/utf8>>/binary,
Type_text/binary>>/binary,
"\". Use a supported SQL type; if this statement should be ignored, remove it from the schema file."/utf8>>}}
end;
{alter_modify_column,
Table_name@4,
Column_name@2,
Type_tokens@1,
Nullable} ->
{Scalar_type@1, New_enums} = resolve_mysql_alter_type(
Type_tokens@1,
Table_name@4,
Column_name@2,
Enums,
Engine
),
Updated_tables = rewrite_column(
Tables,
Table_name@4,
Column_name@2,
fun(C@3) ->
{column, erlang:element(2, C@3), Scalar_type@1, Nullable}
end
),
{ok, {Updated_tables, New_enums}};
{alter_change_column,
Table_name@5,
Old_name@2,
New_name@2,
Type_tokens@2,
Nullable@1} ->
{Scalar_type@2, New_enums@1} = resolve_mysql_alter_type(
Type_tokens@2,
Table_name@5,
New_name@2,
Enums,
Engine
),
Updated_tables@1 = rewrite_column(
Tables,
Table_name@5,
Old_name@2,
fun(_) ->
{column,
string:lowercase(New_name@2),
Scalar_type@2,
Nullable@1}
end
),
{ok, {Updated_tables@1, New_enums@1}};
{alter_column_set_not_null, Table_name@6, Column_name@3} ->
{ok,
{gleam@list:map(
Tables,
fun(T@5) ->
case string:lowercase(erlang:element(2, T@5)) =:= string:lowercase(
Table_name@6
) of
true ->
{table,
erlang:element(2, T@5),
gleam@list:map(
erlang:element(3, T@5),
fun(C@4) ->
case string:lowercase(
erlang:element(2, C@4)
)
=:= string:lowercase(
Column_name@3
) of
true ->
{column,
erlang:element(
2,
C@4
),
erlang:element(
3,
C@4
),
false};
false ->
C@4
end
end
)};
false ->
T@5
end
end
),
Enums}};
{alter_column_drop_not_null, Table_name@7, Column_name@4} ->
{ok,
{gleam@list:map(
Tables,
fun(T@6) ->
case string:lowercase(erlang:element(2, T@6)) =:= string:lowercase(
Table_name@7
) of
true ->
{table,
erlang:element(2, T@6),
gleam@list:map(
erlang:element(3, T@6),
fun(C@5) ->
case string:lowercase(
erlang:element(2, C@5)
)
=:= string:lowercase(
Column_name@4
) of
true ->
{column,
erlang:element(
2,
C@5
),
erlang:element(
3,
C@5
),
true};
false ->
C@5
end
end
)};
false ->
T@6
end
end
),
Enums}}
end.
-file("src/sqlode/internal/schema_parser.gleam", 1566).
?DOC(false).
-spec build_column_from_type_tokens(
binary(),
binary(),
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok,
gleam@option:option({sqlode@internal@model:column(),
list(sqlode@internal@model:enum_def())})} |
{error, parse_error()}.
build_column_from_type_tokens(
Path,
Table_name,
Name,
All_tokens,
Type_toks,
Enums,
Engine
) ->
Type_text = render_type_tokens(Type_toks),
Nullable = case (tokens_contain_not_null(All_tokens) orelse tokens_contain_keyword(
All_tokens,
<<"primary"/utf8>>
))
orelse gleam_stdlib:contains_string(Type_text, <<"serial"/utf8>>) of
true ->
false;
false ->
true
end,
case detect_mysql_inline_enum_set(Type_toks, Table_name, Name, Engine) of
{some, {inline_enum, Scalar_type, New_enum}} ->
{ok, {some, {{column, Name, Scalar_type, Nullable}, [New_enum]}}};
none ->
gleam@result:'try'(case find_enum(Type_text, Enums) of
{some, Enum_name} ->
{ok, {enum_type, Enum_name}};
none ->
_pipe = infer_scalar_type_for_engine(Type_text, Engine),
gleam@result:map_error(
_pipe,
fun(Detail) ->
{invalid_column, Path, Table_name, Detail}
end
)
end, fun(Scalar_type@1) ->
{ok, {some, {{column, Name, Scalar_type@1, Nullable}, []}}}
end)
end.
-file("src/sqlode/internal/schema_parser.gleam", 1481).
?DOC(false).
-spec parse_named_column(
binary(),
binary(),
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok,
gleam@option:option({sqlode@internal@model:column(),
list(sqlode@internal@model:enum_def())})} |
{error, parse_error()}.
parse_named_column(Path, Table_name, Raw_name, All_tokens, Rest, Enums, Engine) ->
Name = sqlode@internal@naming:normalize_identifier(Raw_name),
Type_toks = take_type_tokens_from_lexer(Rest, []),
case Type_toks of
[] ->
{error,
{invalid_column,
Path,
Table_name,
<<"missing type for column "/utf8, Name/binary>>}};
_ ->
build_column_from_type_tokens(
Path,
Table_name,
Name,
All_tokens,
Type_toks,
Enums,
Engine
)
end.
-file("src/sqlode/internal/schema_parser.gleam", 1512).
?DOC(false).
-spec parse_column_tokens(
binary(),
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok,
gleam@option:option({sqlode@internal@model:column(),
list(sqlode@internal@model:enum_def())})} |
{error, parse_error()}.
parse_column_tokens(Path, Table_name, Tokens, Enums, Engine) ->
case Tokens of
[] ->
{ok, none};
[First | Rest] ->
case First of
{keyword, <<"primary"/utf8>>} ->
{ok, none};
{keyword, <<"foreign"/utf8>>} ->
{ok, none};
{keyword, <<"unique"/utf8>>} ->
{ok, none};
{keyword, <<"constraint"/utf8>>} ->
{ok, none};
{keyword, <<"check"/utf8>>} ->
{ok, none};
{keyword, N} ->
parse_named_column(
Path,
Table_name,
N,
Tokens,
Rest,
Enums,
Engine
);
{ident, N@1} ->
Name = sqlode@internal@naming:normalize_identifier(N@1),
Type_toks = take_type_tokens_from_lexer(Rest, []),
case Type_toks of
[] ->
{error,
{invalid_column,
Path,
Table_name,
<<"missing type for column "/utf8,
Name/binary>>}};
_ ->
build_column_from_type_tokens(
Path,
Table_name,
Name,
Tokens,
Type_toks,
Enums,
Engine
)
end;
{quoted_ident, N@1} ->
Name = sqlode@internal@naming:normalize_identifier(N@1),
Type_toks = take_type_tokens_from_lexer(Rest, []),
case Type_toks of
[] ->
{error,
{invalid_column,
Path,
Table_name,
<<"missing type for column "/utf8,
Name/binary>>}};
_ ->
build_column_from_type_tokens(
Path,
Table_name,
Name,
Tokens,
Type_toks,
Enums,
Engine
)
end;
_ ->
{ok, none}
end
end.
-file("src/sqlode/internal/schema_parser.gleam", 604).
?DOC(false).
-spec apply_alter_table_add_column(
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:enum_def()),
list(sqlode@internal@model:table()),
sqlode@internal@model:engine()
) -> {ok,
{list(sqlode@internal@model:table()),
list(sqlode@internal@model:enum_def())}} |
{error, parse_error()}.
apply_alter_table_add_column(Path, Tokens, Enums, Tables, Engine) ->
{Table_name, Col_tokens} = extract_alter_table_parts(Tokens),
case Table_name of
<<""/utf8>> ->
{ok, {Tables, []}};
_ ->
gleam@result:'try'(
parse_column_tokens(Path, Table_name, Col_tokens, Enums, Engine),
fun(Maybe_col) -> case Maybe_col of
none ->
{ok, {Tables, []}};
{some, {Col, New_enums}} ->
{ok,
{gleam@list:map(
Tables,
fun(T) ->
case erlang:element(2, T) =:= Table_name of
true ->
{table,
erlang:element(2, T),
lists:append(
erlang:element(3, T),
[Col]
)};
false ->
T
end
end
),
New_enums}}
end end
)
end.
-file("src/sqlode/internal/schema_parser.gleam", 1408).
?DOC(false).
-spec parse_columns_tokens(
binary(),
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok,
{list(sqlode@internal@model:column()),
list(sqlode@internal@model:enum_def())}} |
{error, parse_error()}.
parse_columns_tokens(Path, Table_name, Tokens, Enums, Engine) ->
_pipe = split_tokens_by_comma(Tokens),
_pipe@1 = gleam@list:try_fold(
_pipe,
{[], []},
fun(Acc, Col_tokens) ->
{Columns, Collected_enums} = Acc,
Visible_enums = lists:append(Enums, Collected_enums),
gleam@result:'try'(
parse_column_tokens(
Path,
Table_name,
Col_tokens,
Visible_enums,
Engine
),
fun(Maybe_column) -> case Maybe_column of
{some, {Column, New_enums}} ->
{ok,
{[Column | Columns],
lists:append(Collected_enums, New_enums)}};
none ->
{ok, Acc}
end end
)
end
),
gleam@result:map(
_pipe@1,
fun(Pair) ->
{lists:reverse(erlang:element(1, Pair)), erlang:element(2, Pair)}
end
).
-file("src/sqlode/internal/schema_parser.gleam", 1330).
?DOC(false).
-spec parse_create_table_tokens(
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok,
gleam@option:option({sqlode@internal@model:table(),
list(sqlode@internal@model:enum_def())})} |
{error, parse_error()}.
parse_create_table_tokens(Path, Tokens, Enums, Engine) ->
{Header, Body} = split_at_lparen(Tokens, []),
case Body of
[] ->
{error,
{invalid_create_table,
Path,
<<"missing opening parenthesis in CREATE TABLE statement"/utf8>>}};
_ ->
gleam@result:'try'(
begin
_pipe = find_last_ident(Header),
gleam@result:map_error(
_pipe,
fun(_) ->
{invalid_create_table,
Path,
<<"missing table name"/utf8>>}
end
)
end,
fun(Table_name) ->
Body_tokens = strip_trailing_rparen(Body),
gleam@result:'try'(
parse_columns_tokens(
Path,
Table_name,
Body_tokens,
Enums,
Engine
),
fun(_use0) ->
{Columns, New_enums} = _use0,
{ok,
{some,
{{table, Table_name, Columns}, New_enums}}}
end
)
end
)
end.
-file("src/sqlode/internal/schema_parser.gleam", 691).
?DOC(false).
-spec parse_statement_tokens(
binary(),
list(sqlode@internal@lexer:token()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok, statement_result()} | {error, parse_error()}.
parse_statement_tokens(Path, Tokens, Enums, Engine) ->
case is_create_table_tokens(Tokens) of
true ->
gleam@result:'try'(
parse_create_table_tokens(Path, Tokens, Enums, Engine),
fun(Maybe_table) -> case Maybe_table of
{some, {Table, New_enums}} ->
{ok, {create_table_result, Table, New_enums}};
none ->
{ok, ignored}
end end
);
false ->
case classify_unknown_statement(Tokens, Engine) of
{destructive_d_d_l, Action} ->
{ok, {d_d_l_applied, Action}};
silently_ignored ->
{ok, ignored};
{unsupported_mysql, Detail} ->
{error, {unsupported_mysql_ddl, Path, Detail}}
end
end.
-file("src/sqlode/internal/schema_parser.gleam", 78).
?DOC(false).
-spec parse_content(
binary(),
binary(),
list(sqlode@internal@model:table()),
list(sqlode@internal@model:enum_def()),
sqlode@internal@model:engine()
) -> {ok, parsed_schema()} | {error, parse_error()}.
parse_content(Path, Content, Existing_tables, Known_enums, Engine) ->
Tokens = sqlode@internal@lexer:tokenize(Content, Engine),
Statements = split_token_statements(Tokens, [], []),
Enums = begin
_pipe = Statements,
gleam@list:filter_map(
_pipe,
fun(Stmt_tokens) -> case is_create_enum_tokens(Stmt_tokens) of
true ->
parse_create_enum_from_tokens(Stmt_tokens);
false ->
{error, nil}
end end
)
end,
All_enums = lists:append(Known_enums, Enums),
Initial_tables = lists:reverse(Existing_tables),
gleam@result:'try'(
begin
_pipe@1 = Statements,
_pipe@2 = gleam@list:try_fold(
_pipe@1,
{Initial_tables, All_enums, []},
fun(Acc, Stmt_tokens@1) ->
{Tables, Current_enums, Warnings} = Acc,
case is_create_view_tokens(Stmt_tokens@1) of
true ->
{Maybe_table, View_warnings} = parse_create_view_from_tokens(
Stmt_tokens@1,
lists:reverse(Tables)
),
New_warnings = lists:append(Warnings, View_warnings),
{ok, case Maybe_table of
{some, Table} ->
{[Table | Tables],
Current_enums,
New_warnings};
none ->
{Tables, Current_enums, New_warnings}
end};
false ->
case is_alter_table_add_column_tokens(Stmt_tokens@1) of
true ->
gleam@result:'try'(
apply_alter_table_add_column(
Path,
Stmt_tokens@1,
Current_enums,
Tables,
Engine
),
fun(_use0) ->
{New_tables, Added_enums} = _use0,
{ok,
{New_tables,
lists:append(
Current_enums,
Added_enums
),
Warnings}}
end
);
false ->
gleam@result:'try'(
parse_statement_tokens(
Path,
Stmt_tokens@1,
Current_enums,
Engine
),
fun(Stmt_result) -> case Stmt_result of
{create_table_result,
Table@1,
New_enums} ->
{ok,
{[Table@1 | Tables],
lists:append(
Current_enums,
New_enums
),
Warnings}};
{d_d_l_applied, Action} ->
gleam@result:'try'(
apply_ddl_action(
Action,
Path,
Tables,
Current_enums,
Engine
),
fun(_use0@1) ->
{New_tables@1,
New_enums@1} = _use0@1,
{ok,
{New_tables@1,
New_enums@1,
Warnings}}
end
);
ignored ->
{ok,
{Tables,
Current_enums,
Warnings}}
end end
)
end
end
end
),
gleam@result:map(
_pipe@2,
fun(Triple) ->
{lists:reverse(erlang:element(1, Triple)),
erlang:element(2, Triple),
erlang:element(3, Triple)}
end
)
end,
fun(_use0@2) ->
{Tables@1, Final_enums, Warnings@1} = _use0@2,
{ok, {parsed_schema, Tables@1, Final_enums, Warnings@1}}
end
).
-file("src/sqlode/internal/schema_parser.gleam", 43).
?DOC(false).
-spec parse_files_with_engine(
list({binary(), binary()}),
sqlode@internal@model:engine()
) -> {ok, {sqlode@internal@model:catalog(), list(schema_warning())}} |
{error, parse_error()}.
parse_files_with_engine(Entries, Engine) ->
_pipe = Entries,
_pipe@1 = gleam@list:try_fold(
_pipe,
{parsed_schema, [], [], []},
fun(Acc, Entry) ->
{Path, Content} = Entry,
gleam@result:'try'(
parse_content(
Path,
Content,
erlang:element(2, Acc),
erlang:element(3, Acc),
Engine
),
fun(Parsed) ->
{ok,
{parsed_schema,
erlang:element(2, Parsed),
erlang:element(3, Parsed),
lists:append(
erlang:element(4, Acc),
erlang:element(4, Parsed)
)}}
end
)
end
),
gleam@result:map(
_pipe@1,
fun(Schema) ->
{{catalog, erlang:element(2, Schema), erlang:element(3, Schema)},
erlang:element(4, Schema)}
end
).
-file("src/sqlode/internal/schema_parser.gleam", 37).
?DOC(false).
-spec parse_files(list({binary(), binary()})) -> {ok,
{sqlode@internal@model:catalog(), list(schema_warning())}} |
{error, parse_error()}.
parse_files(Entries) ->
parse_files_with_engine(Entries, postgre_s_q_l).
-file("src/sqlode/internal/schema_parser.gleam", 1806).
?DOC(false).
-spec path_prefix(binary()) -> binary().
path_prefix(Path) ->
case Path of
<<""/utf8>> ->
<<""/utf8>>;
_ ->
<<Path/binary, ": "/utf8>>
end.
-file("src/sqlode/internal/schema_parser.gleam", 1791).
?DOC(false).
-spec error_to_string(parse_error()) -> binary().
error_to_string(Error) ->
case Error of
{invalid_create_table, Path, Detail} ->
<<<<(path_prefix(Path))/binary,
"Invalid CREATE TABLE statement: "/utf8>>/binary,
Detail/binary>>;
{invalid_column, Path@1, Table, Detail@1} ->
<<<<<<<<(path_prefix(Path@1))/binary,
"Invalid column definition in table "/utf8>>/binary,
Table/binary>>/binary,
": "/utf8>>/binary,
Detail@1/binary>>;
{unsupported_mysql_ddl, Path@2, Detail@2} ->
<<<<(path_prefix(Path@2))/binary,
"Unsupported MySQL DDL statement: "/utf8>>/binary,
Detail@2/binary>>
end.
-file("src/sqlode/internal/schema_parser.gleam", 1813).
?DOC(false).
-spec warning_to_string(schema_warning()) -> binary().
warning_to_string(Warning) ->
{unresolvable_view_column, Column} = Warning,
<<<<"Warning: view column \""/utf8, Column/binary>>/binary,
"\" could not be resolved from source tables — skipping column."/utf8>>.