-module(sqlode@schema_parser). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sqlode/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]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type parse_error() :: {invalid_create_table, binary(), binary()} | {invalid_column, binary(), binary(), binary()}. -type schema_warning() :: {unresolvable_view_column, binary()}. -type parsed_schema() :: {parsed_schema, list(sqlode@model:table()), list(sqlode@model:enum_def()), list(schema_warning())}. -type view_column() :: {view_column, binary(), list(sqlode@lexer:token())}. -type statement_result() :: {create_table_result, sqlode@model:table()} | {d_d_l_applied, d_d_l_action()} | ignored. -type unknown_statement_kind() :: {destructive_d_d_l, d_d_l_action()} | silently_ignored. -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@lexer:token())} | {alter_column_set_not_null, binary(), binary()} | {alter_column_drop_not_null, binary(), binary()}. -file("src/sqlode/schema_parser.gleam", 136). ?DOC(" Split token list on Semicolon tokens into a list of statements.\n"). -spec split_token_statements( list(sqlode@lexer:token()), list(sqlode@lexer:token()), list(list(sqlode@lexer:token())) ) -> list(list(sqlode@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/schema_parser.gleam", 157). ?DOC(" Check if a token list represents a CREATE TYPE ... AS ENUM statement.\n"). -spec is_create_enum_tokens(list(sqlode@lexer:token())) -> boolean(). is_create_enum_tokens(Tokens) -> case Tokens of [{keyword, <<"create"/utf8>>}, {keyword, <<"type"/utf8>>}, _, {keyword, <<"as"/utf8>>}, {keyword, <<"enum"/utf8>>} | _] -> true; _ -> false end. -file("src/sqlode/schema_parser.gleam", 197). ?DOC(" Extract string literal values from inside ENUM parentheses.\n"). -spec extract_enum_values(list(sqlode@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/schema_parser.gleam", 172). ?DOC(" Parse a CREATE TYPE name AS ENUM (...) from tokens, handling escaped quotes correctly.\n"). -spec parse_create_enum_from_tokens(list(sqlode@lexer:token())) -> {ok, sqlode@model:enum_def()} | {error, nil}. parse_create_enum_from_tokens(Tokens) -> case Tokens of [{keyword, <<"create"/utf8>>}, {keyword, <<"type"/utf8>>}, Name_token, {keyword, <<"as"/utf8>>}, {keyword, <<"enum"/utf8>>} | Rest] -> 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}}; _ -> {error, nil} end. -file("src/sqlode/schema_parser.gleam", 209). -spec is_create_view_tokens(list(sqlode@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/schema_parser.gleam", 264). -spec extract_view_name_result(list(sqlode@lexer:token())) -> gleam@option:option({binary(), list(sqlode@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/schema_parser.gleam", 285). -spec extract_after_select_result(list(sqlode@lexer:token())) -> gleam@option:option(list(sqlode@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/schema_parser.gleam", 333). -spec skip_to_keyword(list(sqlode@lexer:token()), binary()) -> list(sqlode@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/schema_parser.gleam", 344). -spec split_at_from( list(sqlode@lexer:token()), integer(), list(sqlode@lexer:token()) ) -> {list(sqlode@lexer:token()), list(sqlode@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/schema_parser.gleam", 479). -spec infer_cast_type(list(sqlode@lexer:token())) -> gleam@option:option({sqlode@model:scalar_type(), boolean()}). infer_cast_type(Tokens) -> case Tokens of [] -> none; [{keyword, <<"as"/utf8>>}, {ident, Type_name} | _] -> case sqlode@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@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/schema_parser.gleam", 494). -spec tok_split_select_columns( list(sqlode@lexer:token()), integer(), list(sqlode@lexer:token()), list(list(sqlode@lexer:token())) ) -> list(list(sqlode@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/schema_parser.gleam", 360). -spec extract_view_columns(list(sqlode@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/schema_parser.gleam", 518). ?DOC(" Detect ALTER TABLE ... ADD [COLUMN] pattern.\n"). -spec is_alter_table_add_column_tokens(list(sqlode@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/schema_parser.gleam", 607). -spec extract_ident(sqlode@lexer:token()) -> binary(). extract_ident(Token) -> case Token of {ident, N} -> sqlode@naming:normalize_identifier(N); {quoted_ident, N@1} -> sqlode@naming:normalize_identifier(N@1); _ -> <<""/utf8>> end. -file("src/sqlode/schema_parser.gleam", 584). -spec extract_alter_table_parts(list(sqlode@lexer:token())) -> {binary(), list(sqlode@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), Rest}; [{keyword, <<"alter"/utf8>>}, {keyword, <<"table"/utf8>>}, Name_tok@1, {keyword, <<"add"/utf8>>} | Rest@1] -> {extract_ident(Name_tok@1), Rest@1}; _ -> {<<""/utf8>>, []} end. -file("src/sqlode/schema_parser.gleam", 702). ?DOC(" Parse DROP TABLE [IF EXISTS] from the token list.\n"). -spec parse_drop_table(list(sqlode@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/schema_parser.gleam", 713). ?DOC(" Parse DROP VIEW [IF EXISTS] from the token list.\n"). -spec parse_drop_view(list(sqlode@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/schema_parser.gleam", 724). ?DOC(" Parse DROP TYPE [IF EXISTS] from the token list.\n"). -spec parse_drop_type(list(sqlode@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/schema_parser.gleam", 753). -spec skip_alter_modifiers(list(sqlode@lexer:token())) -> {list(sqlode@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/schema_parser.gleam", 809). ?DOC(" Extract the column name from ALTER [COLUMN] ... tokens.\n"). -spec extract_alter_column_name(list(sqlode@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/schema_parser.gleam", 818). ?DOC(" Extract TYPE tokens from ALTER [COLUMN] [SET DATA] TYPE .\n"). -spec extract_alter_type_tokens(list(sqlode@lexer:token())) -> list(sqlode@lexer:token()). extract_alter_type_tokens(Tokens) -> case Tokens of [] -> []; [{keyword, <<"type"/utf8>>} | Rest] -> Rest; [_ | Rest@1] -> extract_alter_type_tokens(Rest@1) end. -file("src/sqlode/schema_parser.gleam", 962). -spec token_keyword_text(sqlode@lexer:token()) -> binary(). token_keyword_text(Token) -> case Token of {keyword, K} -> K; {ident, I} -> string:lowercase(I); _ -> <<""/utf8>> end. -file("src/sqlode/schema_parser.gleam", 762). -spec classify_alter_action_tokens(binary(), list(sqlode@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)}}; [<<"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/schema_parser.gleam", 735). ?DOC(" Classify ALTER TABLE statements from the full token list.\n"). -spec classify_alter_table_from_tokens(list(sqlode@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/schema_parser.gleam", 677). ?DOC( " Classify a statement that is not a CREATE TABLE / VIEW / ENUM and not an\n" " ALTER TABLE ... ADD COLUMN. The intent is to let informational or\n" " out-of-scope DDL through silently (CREATE INDEX, COMMENT ON, transaction\n" " control) while failing fast on DDL that materially changes the catalog\n" " and would otherwise be missed (DROP TABLE, ALTER TABLE DROP/RENAME/ALTER).\n" "\n" " The lexer reserves only a subset of SQL words as `Keyword` tokens. Words\n" " like `rename`, `savepoint`, and `comment` arrive as `Ident` tokens, so\n" " we normalise the first few tokens of the statement to lowercase strings\n" " before matching, treating Keyword and Ident uniformly.\n" ). -spec classify_unknown_statement(list(sqlode@lexer:token())) -> unknown_statement_kind(). classify_unknown_statement(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 [<<"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); _ -> silently_ignored end. -file("src/sqlode/schema_parser.gleam", 970). -spec is_create_table_tokens(list(sqlode@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/schema_parser.gleam", 991). -spec find_column_in_tables(list(sqlode@model:table()), binary()) -> gleam@option:option(sqlode@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/schema_parser.gleam", 390). -spec resolve_column_from_expr_tokens( list(sqlode@lexer:token()), list(sqlode@model:table()) ) -> gleam@option:option(sqlode@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/schema_parser.gleam", 462). -spec extract_aggregate_inner_type( list(sqlode@lexer:token()), list(sqlode@model:table()) ) -> gleam@option:option(sqlode@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/schema_parser.gleam", 406). -spec infer_view_expression_type( list(sqlode@lexer:token()), list(sqlode@model:table()) ) -> gleam@option:option({sqlode@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/schema_parser.gleam", 313). -spec resolve_single_view_column( binary(), list(sqlode@lexer:token()), list(sqlode@model:table()) ) -> {ok, sqlode@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/schema_parser.gleam", 298). -spec resolve_view_columns( list(sqlode@lexer:token()), list(sqlode@model:table()) ) -> {list(sqlode@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@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/schema_parser.gleam", 223). -spec parse_create_view_from_tokens( list(sqlode@lexer:token()), list(sqlode@model:table()) ) -> {gleam@option:option(sqlode@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@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/schema_parser.gleam", 1041). ?DOC(" Split tokens at the first top-level LParen. Returns (header, body_after_lparen).\n"). -spec split_at_lparen(list(sqlode@lexer:token()), list(sqlode@lexer:token())) -> {list(sqlode@lexer:token()), list(sqlode@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/schema_parser.gleam", 1053). ?DOC(" Find the last Ident or QuotedIdent token in a list and return its name.\n"). -spec find_last_ident(list(sqlode@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@naming:normalize_identifier(N)}; {quoted_ident, N} -> {ok, sqlode@naming:normalize_identifier(N)}; _ -> {error, nil} end end), gleam@list:last(_pipe@1). -file("src/sqlode/schema_parser.gleam", 1073). -spec drop_trailing_rparens(list(sqlode@lexer:token())) -> list(sqlode@lexer:token()). drop_trailing_rparens(Rev_tokens) -> case Rev_tokens of [r_paren | Rest] -> Rest; _ -> Rev_tokens end. -file("src/sqlode/schema_parser.gleam", 1066). ?DOC(" Strip trailing RParen from token list.\n"). -spec strip_trailing_rparen(list(sqlode@lexer:token())) -> list(sqlode@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/schema_parser.gleam", 1107). -spec split_tokens_by_comma_loop( list(sqlode@lexer:token()), integer(), list(sqlode@lexer:token()), list(list(sqlode@lexer:token())) ) -> list(list(sqlode@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/schema_parser.gleam", 1103). ?DOC(" Split a token list by top-level commas (depth-0 only).\n"). -spec split_tokens_by_comma(list(sqlode@lexer:token())) -> list(list(sqlode@lexer:token())). split_tokens_by_comma(Tokens) -> split_tokens_by_comma_loop(Tokens, 0, [], []). -file("src/sqlode/schema_parser.gleam", 1221). -spec render_type_tokens_loop(list(sqlode@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_rest] ); [] -> render_type_tokens_loop(Rest, [<<"[]"/utf8>> | Acc]) end; [Tok | Rest@1] -> S = 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 S of <<""/utf8>> -> render_type_tokens_loop(Rest@1, Acc); _ -> render_type_tokens_loop(Rest@1, [S | Acc]) end end. -file("src/sqlode/schema_parser.gleam", 1215). ?DOC( " Render type tokens back to a type string for parse_sql_type lookup.\n" " Handles array syntax ([] operators) by joining without spaces.\n" ). -spec render_type_tokens(list(sqlode@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/schema_parser.gleam", 827). ?DOC(" Apply a destructive DDL action to the tables and enums lists.\n"). -spec apply_ddl_action( d_d_l_action(), list(sqlode@model:table()), list(sqlode@model:enum_def()) ) -> {list(sqlode@model:table()), list(sqlode@model:enum_def())}. apply_ddl_action(Action, Tables, Enums) -> case Action of {drop_table, Table_name} -> {gleam@list:filter( Tables, fun(T) -> string:lowercase(erlang:element(2, T)) /= string:lowercase( Table_name ) end ), Enums}; {drop_view, View_name} -> {gleam@list:filter( Tables, fun(T@1) -> string:lowercase(erlang:element(2, T@1)) /= string:lowercase( View_name ) end ), Enums}; {drop_type, Type_name} -> {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} -> {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} -> {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} -> {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), New_type = case sqlode@model:parse_sql_type(Type_text) of {ok, T@5} -> {some, T@5}; {error, _} -> none end, case New_type of {some, Scalar_type} -> {gleam@list:map( Tables, fun(T@6) -> case string:lowercase(erlang:element(2, T@6)) =:= string:lowercase(Table_name@3) of true -> {table, erlang:element(2, T@6), gleam@list:map( erlang:element(3, T@6), fun(C@2) -> case string:lowercase( erlang:element(2, C@2) ) =:= string:lowercase( Column_name@1 ) of true -> {column, erlang:element( 2, C@2 ), Scalar_type, erlang:element( 4, C@2 )}; false -> C@2 end end )}; false -> T@6 end end ), Enums}; none -> {Tables, Enums} end; {alter_column_set_not_null, Table_name@4, Column_name@2} -> {gleam@list:map( Tables, fun(T@7) -> case string:lowercase(erlang:element(2, T@7)) =:= string:lowercase( Table_name@4 ) of true -> {table, erlang:element(2, T@7), gleam@list:map( erlang:element(3, T@7), fun(C@3) -> case string:lowercase( erlang:element(2, C@3) ) =:= string:lowercase(Column_name@2) of true -> {column, erlang:element(2, C@3), erlang:element(3, C@3), false}; false -> C@3 end end )}; false -> T@7 end end ), Enums}; {alter_column_drop_not_null, Table_name@5, Column_name@3} -> {gleam@list:map( Tables, fun(T@8) -> case string:lowercase(erlang:element(2, T@8)) =:= string:lowercase( Table_name@5 ) of true -> {table, erlang:element(2, T@8), gleam@list:map( erlang:element(3, T@8), 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), true}; false -> C@4 end end )}; false -> T@8 end end ), Enums} end. -file("src/sqlode/schema_parser.gleam", 1255). -spec tokens_contain_not_null(list(sqlode@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/schema_parser.gleam", 1263). -spec tokens_contain_keyword(list(sqlode@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/schema_parser.gleam", 1272). -spec infer_scalar_type(binary()) -> {ok, sqlode@model:scalar_type()} | {error, binary()}. infer_scalar_type(Type_text) -> _pipe = sqlode@model:parse_sql_type(Type_text), gleam@result:replace_error( _pipe, <<<<<<<<"unrecognized SQL type \""/utf8, Type_text/binary>>/binary, "\". Supported types: int, serial, float, numeric, bool, text, char, bytea,"/utf8>>/binary, " uuid, json, jsonb, timestamp, datetime, date, time, interval."/utf8>>/binary, " Hint: add a type override in sqlode.yaml under overrides.db_type"/utf8>> ). -file("src/sqlode/schema_parser.gleam", 1283). -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>>], Keyword ). -file("src/sqlode/schema_parser.gleam", 1198). -spec take_type_tokens_from_lexer( list(sqlode@lexer:token()), list(sqlode@lexer:token()) ) -> list(sqlode@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; [Tok | Rest@1] -> take_type_tokens_from_lexer(Rest@1, [Tok | Acc]) end. -file("src/sqlode/schema_parser.gleam", 1302). -spec find_enum(binary(), list(sqlode@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/schema_parser.gleam", 1143). -spec parse_column_tokens( binary(), binary(), list(sqlode@lexer:token()), list(sqlode@model:enum_def()) ) -> {ok, gleam@option:option(sqlode@model:column())} | {error, parse_error()}. parse_column_tokens(Path, Table_name, Tokens, Enums) -> 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}; {ident, N} -> Name = sqlode@naming:normalize_identifier(N), 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>>}}; _ -> Type_text = render_type_tokens(Type_toks), Nullable = case (tokens_contain_not_null(Tokens) orelse tokens_contain_keyword( Tokens, <<"primary"/utf8>> )) orelse gleam_stdlib:contains_string( Type_text, <<"serial"/utf8>> ) of true -> false; false -> true end, gleam@result:'try'( case find_enum(Type_text, Enums) of {some, Enum_name} -> {ok, {enum_type, Enum_name}}; none -> _pipe = infer_scalar_type(Type_text), gleam@result:map_error( _pipe, fun(Detail) -> {invalid_column, Path, Table_name, Detail} end ) end, fun(Scalar_type) -> {ok, {some, {column, Name, Scalar_type, Nullable}}} end ) end; {quoted_ident, N} -> Name = sqlode@naming:normalize_identifier(N), 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>>}}; _ -> Type_text = render_type_tokens(Type_toks), Nullable = case (tokens_contain_not_null(Tokens) orelse tokens_contain_keyword( Tokens, <<"primary"/utf8>> )) orelse gleam_stdlib:contains_string( Type_text, <<"serial"/utf8>> ) of true -> false; false -> true end, gleam@result:'try'( case find_enum(Type_text, Enums) of {some, Enum_name} -> {ok, {enum_type, Enum_name}}; none -> _pipe = infer_scalar_type(Type_text), gleam@result:map_error( _pipe, fun(Detail) -> {invalid_column, Path, Table_name, Detail} end ) end, fun(Scalar_type) -> {ok, {some, {column, Name, Scalar_type, Nullable}}} end ) end; _ -> {ok, none} end end. -file("src/sqlode/schema_parser.gleam", 551). ?DOC(" Parse ALTER TABLE ADD [COLUMN] and apply to existing tables.\n"). -spec apply_alter_table_add_column( binary(), list(sqlode@lexer:token()), list(sqlode@model:enum_def()), list(sqlode@model:table()) ) -> {ok, list(sqlode@model:table())} | {error, parse_error()}. apply_alter_table_add_column(Path, Tokens, Enums, Tables) -> {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), fun(Maybe_col) -> case Maybe_col of none -> {ok, Tables}; {some, Col} -> {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 )} end end ) end. -file("src/sqlode/schema_parser.gleam", 1080). -spec parse_columns_tokens( binary(), binary(), list(sqlode@lexer:token()), list(sqlode@model:enum_def()) ) -> {ok, list(sqlode@model:column())} | {error, parse_error()}. parse_columns_tokens(Path, Table_name, Tokens, Enums) -> _pipe = split_tokens_by_comma(Tokens), _pipe@1 = gleam@list:try_fold( _pipe, [], fun(Columns, Col_tokens) -> gleam@result:'try'( parse_column_tokens(Path, Table_name, Col_tokens, Enums), fun(Maybe_column) -> {ok, case Maybe_column of {some, Column} -> [Column | Columns]; none -> Columns end} end ) end ), gleam@result:map(_pipe@1, fun lists:reverse/1). -file("src/sqlode/schema_parser.gleam", 1004). -spec parse_create_table_tokens( binary(), list(sqlode@lexer:token()), list(sqlode@model:enum_def()) ) -> {ok, gleam@option:option(sqlode@model:table())} | {error, parse_error()}. parse_create_table_tokens(Path, Tokens, Enums) -> {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 ), fun(Columns) -> {ok, {some, {table, Table_name, Columns}}} end ) end ) end. -file("src/sqlode/schema_parser.gleam", 621). -spec parse_statement_tokens( binary(), list(sqlode@lexer:token()), list(sqlode@model:enum_def()) ) -> {ok, statement_result()} | {error, parse_error()}. parse_statement_tokens(Path, Tokens, Enums) -> case is_create_table_tokens(Tokens) of true -> gleam@result:'try'( parse_create_table_tokens(Path, Tokens, Enums), fun(Maybe_table) -> case Maybe_table of {some, Table} -> {ok, {create_table_result, Table}}; none -> {ok, ignored} end end ); false -> case classify_unknown_statement(Tokens) of {destructive_d_d_l, Action} -> {ok, {d_d_l_applied, Action}}; silently_ignored -> {ok, ignored} end end. -file("src/sqlode/schema_parser.gleam", 62). -spec parse_content( binary(), binary(), list(sqlode@model:enum_def()), sqlode@model:engine() ) -> {ok, parsed_schema()} | {error, parse_error()}. parse_content(Path, Content, Known_enums, Engine) -> Tokens = sqlode@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), gleam@result:'try'( begin _pipe@1 = Statements, _pipe@2 = gleam@list:try_fold( _pipe@1, {[], 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 ), fun(New_tables) -> {ok, {New_tables, Current_enums, Warnings}} end ); false -> gleam@result:'try'( parse_statement_tokens( Path, Stmt_tokens@1, Current_enums ), fun(Stmt_result) -> case Stmt_result of {create_table_result, Table@1} -> {ok, {[Table@1 | Tables], Current_enums, Warnings}}; {d_d_l_applied, Action} -> {New_tables@1, New_enums} = apply_ddl_action( Action, Tables, Current_enums ), {ok, {New_tables@1, New_enums, Warnings}}; 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) -> {Tables@1, Final_enums, Warnings@1} = _use0, {ok, {parsed_schema, Tables@1, Final_enums, Warnings@1}} end ). -file("src/sqlode/schema_parser.gleam", 37). -spec parse_files_with_engine(list({binary(), binary()}), sqlode@model:engine()) -> {ok, {sqlode@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(3, Acc), Engine), fun(Parsed) -> {ok, {parsed_schema, lists:append( erlang:element(2, Acc), erlang:element(2, Parsed) ), lists:append( erlang:element(3, Acc), 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/schema_parser.gleam", 31). -spec parse_files(list({binary(), binary()})) -> {ok, {sqlode@model:catalog(), list(schema_warning())}} | {error, parse_error()}. parse_files(Entries) -> parse_files_with_engine(Entries, postgre_s_q_l). -file("src/sqlode/schema_parser.gleam", 1324). -spec path_prefix(binary()) -> binary(). path_prefix(Path) -> case Path of <<""/utf8>> -> <<""/utf8>>; _ -> <> end. -file("src/sqlode/schema_parser.gleam", 1311). -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>> end. -file("src/sqlode/schema_parser.gleam", 1331). -spec warning_to_string(schema_warning()) -> binary(). warning_to_string(Warning) -> case Warning of {unresolvable_view_column, Column} -> <<<<"Warning: view column \""/utf8, Column/binary>>/binary, "\" could not be resolved from source tables — skipping column."/utf8>> end.